👍SpringSecurity单体项目最佳实践
SpringSecurity单体项目最佳实践
到这里,我们的SpringSecurity就已经完结啦,文章中可能有些地方不能做到全面覆盖,视频教程地址
1、搭建环境
建议下载初始项目,跟着文章一步一步搭建。加深对于
SpringSecurity
的理解。需要将
application.properties
的数据库配置,改成您自己对应的信息如若依赖问题,修改Idea Maven,改成自己的
还需将Jdk版本改成您自己所使用的的版本。项目使用的是
JDK12
数据库脚本在完成项目中的sql文件中
2、简单使用
添加
SpringSecurity
依赖注:这里没有申明版本号,是由于我们项目继承的SpringBoot父项目,它已经为我们适配了对于的版本。
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-security</artifactId>
</dependency>
然后启动项目即可:
- ️ 观察控制台:这是
SpringSecurity
为我们临时生成的密码,默认用户名为user
- ️ 回到浏览器,输入
http://localhost:8080/community
,由于我们此时还未登陆,会重定向到默认创建的登陆页面中,这是SpringSecurity默认为我们做的。 - 输入控制台的密码,即可进入到系统,
3、自定义使用
- 相信小伙伴们已经对
SpringSecurity
已经有了初步的了解,但是正常的项目中,不可能采用这个默认登陆页面呀,这点SpringSecurity
也早就想到了。 - 当然可以自定义登陆页面,但是在自定义登陆页面之前,我们需要简单处理一下我们的实体类。
在用户登录时,系统会根据用户名,从存储设备查找该用户的密码及权限等,将其组装成一个
UserDetails
对象。并用UserDetails
中的数据对用户进行认证,决定其输入的用户名/密码是否正确。
- 观察
UserDetails
结构
public interface UserDetails extends Serializable {
Collection<? extends GrantedAuthority> getAuthorities();//权限
String getPassword(); //密码
String getUsername(); //用户名
boolean isAccountNonExpired(); //账号是否未过期
boolean isAccountNonLocked(); //账号是否未锁定
boolean isCredentialsNonExpired();//密码是否未过期
boolean isEnabled(); //是否激活
- 里面定义了许多关于用户的信息,可以看到它是一个接口,并不能直接使用。那么肯定就有默认的实现类,要不然我们上面的登陆功能是怎么完成的呢。
- ️ 此项目中采用 实体类继承它的方式来完成。
@Data
public class User implements UserDetails {
private int id;
private String username;
private String password;
private String salt;
private String email;
private int type; // 1 管理员 2普通用户
private int status;
private String activationCode;
private String headerUrl;
private Date createTime;
//权限
@Override
public Collection<? extends GrantedAuthority> getAuthorities() {
List<GrantedAuthority> permissions = new ArrayList<>();
permissions.add((GrantedAuthority) () -> {
switch (type) { //
case 1:
return "ADMIN";
default:
return "USER";
}
});
return permissions;
}
// true 帐户未过期
@Override
public boolean isAccountNonExpired() {
return true;
}
// true 帐户未锁定
@Override
public boolean isAccountNonLocked() {
return true;
}
// true 凭证未过期
@Override
public boolean isCredentialsNonExpired() {
return true;
}
// true 账号是否可用
@Override
public boolean isEnabled() {
return true;
}
}
- ️ 定义了UserDetails之后,当然还远远不够,哪个方法查询数据库来获取我们的用户信息呢?就是Security中的
UserDetailsService
接口
- ️ 它肯定也有默认实现类的,但是我们需要查询数据库对应的用户数据,所以我们还是采用
自定义
的方式去完成。
@Service
public class UserService implements UserDetailsService {
@Resource
private UserMapper userMapper;
// 根据用户名去查询用户数据
public User findUserByName(String username) {
return userMapper.selectByName(username);
}
@Override
public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
return this.findUserByName(username);
}
}
- 完成到这里,对于用户信息的功能已经实现,但是我们还没有配置我们的登陆界面。
配置Security
- ️ 在
config
目录下创建SecurityConfig
@Configuration
public class SecurityConfig extends WebSecurityConfigurerAdapter {
@Resource
private UserService userService;
}
- ️ 正常项目中,肯定会有许多的静态资源,这些都可以在不登录的情况下访问,如css、js等
@Override
public void configure(WebSecurity web) throws Exception {
// 忽略静态资源
web.ignoring().antMatchers("/resources/**");
}
- ️ 当然我们上面的
UserService
只实现了认证的查询,并没有配置在何时去调用这个类。
认证规则二选其一即可
// AuthenticationManager: 认证的核心接口
// AuthenticationManagerBuilder: 用户构建AuthenticationManager对象的工厂类
// ProviderManager: AuthenticationManager默认使用的实现类
@Override
protected void configure(AuthenticationManagerBuilder auth) throws Exception {
//内置的认证规则
//auth.userDetailsService(userService).passwordEncoder(new BCryptPasswordEncoder());
// 自定义认证规则
// AuthenticationProvider: ProviderManager持有一组AuthenticationProvider,每个AuthenticationProvider负责一种认证
// 委托模式:
// AuthenticationProvider: 就好比登陆方式,不仅有密码登录,且还有微信,等其他登陆方式,每一种登陆方式对应一个AuthenticationProvider
auth.authenticationProvider(new AuthenticationProvider() {
// Authentication: 用于封装认证信息的接口,不同实现类代表不同类型的认证信息
@Override
public Authentication authenticate(Authentication authentication) throws AuthenticationException {
String username = authentication.getName();
String password = authentication.getCredentials().toString();
User user = userService.findUserByName(username);
if (user == null) {
throw new UsernameNotFoundException("账号或密码错误!");
}
password = CommunityUtil.md5(password + user.getSalt());
if (!user.getPassword().equals(password)) {
throw new BadCredentialsException("账号或密码错误!");
}
// principal:认证的主要信息 credentials:代表用户 authorities:权限信息
return new UsernamePasswordAuthenticationToken(user, user.getPassword(), user.getAuthorities());
}
// 当前的AuthenticationProvider 支持哪种类型的认证。
@Override
public boolean supports(Class<?> aClass) {
// UsernamePasswordAuthenticationToken: Authentication接口常用的实现类
// 这样配置,我们当前项目只支持UsernamePasswordAuthenticationToken的认证
return UsernamePasswordAuthenticationToken.class.equals(aClass);
}
});
}
- ️ 配置了以上步骤,是不是觉得Security挺麻烦的,别急马上到头了。
@Override
protected void configure(HttpSecurity http) throws Exception {
// 登陆相关配置
http.formLogin()
.loginPage("/loginpage") // 登陆页面
.loginProcessingUrl("/login") // 处理登陆请求的路径
.successHandler(new AuthenticationSuccessHandler() { // 认证成功处理器
@Override
public void onAuthenticationSuccess(HttpServletRequest request, HttpServletResponse response, Authentication authentication) throws IOException, ServletException {
// 重定向到主页面
response.sendRedirect(request.getContextPath() + "/index");
}
})
.failureHandler(new AuthenticationFailureHandler() { // 认证失败处理器
@Override
public void onAuthenticationFailure(HttpServletRequest request, HttpServletResponse response, AuthenticationException e) throws IOException, ServletException {
// 请求转发到登陆页面
// 因为在项目中,登陆失败后,往往需要携带错误信息到页面展示,所有采用请求转发的方式
request.setAttribute("error", e.getMessage());
request.getRequestDispatcher("/loginpage").forward(request, response);
}
});
// 退出相关配置
http.logout().logoutUrl("/logout").logoutSuccessUrl("/index"); // 退出后重定向到的接口
// 授权配置 配置什么路径只能什么权限访问
http.authorizeRequests()
.antMatchers("/letter").hasAnyAuthority("USER", "ADMIN")
.antMatchers("/admin").hasAnyAuthority("ADMIN")
.and().exceptionHandling().accessDeniedPage("/denied"); //无权限时,重回定向到的页面
}
- 现在对于后端的配置就完成啦,前端界面建议直接从完成的项目中copy
- ️ 一定要检查数据库有没有对应的用户数据哦!!!
- 接下来就是你们的时间啦。自行测试
- 但是正常的项目中,登陆功能一定会有验证码的存在,SpringSecurity也想到了这一点,我们都知道SpringSecurity是由一大串过滤器来完成对应功能的,也就是说,我们需要在登陆校验之前完成对于验证码的校验。如下:
// 增加Filter 处理验证码
http.addFilterBefore(new Filter() {
@Override
public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException {
HttpServletRequest request = (HttpServletRequest) servletRequest;
HttpServletResponse response = (HttpServletResponse) servletResponse;
if (request.getServletPath().equals("/login")) {
// 正常项目中 验证码会存储在Session或者Redis中,为了方便。此项目的验证码都是1234
String verifyCode = request.getParameter("verifyCode");
if (!"1234".equals(verifyCode)) {
request.setAttribute("error", "验证码错误!");
request.getRequestDispatcher("/loginpage").forward(request, response);
return;
}
}
// 放行请求,执行到下一个过滤器
filterChain.doFilter(request, response);
}
}, UsernamePasswordAuthenticationFilter.class);
// 记住我功能
http.rememberMe()
.tokenRepository(new InMemoryTokenRepositoryImpl()) // 用户的令牌存储到哪,InMemoryTokenRepositoryImpl 存储到内存中
.tokenValiditySeconds(3600 * 24) // 过期时间
.userDetailsService(userService);// 当关闭浏览器后,第二次访问,去拿重新查询用户的数据
到这里,我们的SpringSecurity就已经完结啦,文章中可能有些地方不能做到全面覆盖,视频教程地址
👍SpringSecurity单体项目最佳实践的更多相关文章
- 基于 Lerna 管理 packages 的 Monorepo 项目最佳实践
本文首发于 vivo互联网技术 微信公众号 https://mp.weixin.qq.com/s/NlOn7er0ixY1HO40dq5Gag作者:孔垂亮 目录 一.背景二.Monorepo vs M ...
- 在 Kubernetes 容器集群,微服务项目最佳实践
转载自:https://mp.weixin.qq.com/s/WYu3gDwKKf06f_FYbO9YRg 本文主要介绍我个人在使用 Kubernetes 的过程中,总结出的一套「Kubernetes ...
- 基于springboot的web项目最佳实践
springboot 可以说是现在做javaweb开发最火的技术,我在基于springboot搭建项目的过程中,踩过不少坑,发现整合框架时并非仅仅引入starter 那么简单. 要做到简单,易用,扩展 ...
- Go单体服务开发最佳实践
单体最佳实践的由来 对于很多初创公司来说,业务的早期我们更应该关注于业务价值的交付,并且此时用户体量也很小,QPS 也非常低,我们应该使用更简单的技术架构来加速业务价值的交付,此时单体的优势就体现出来 ...
- Django 1.6 最佳实践: 如何设置django项目的设置(settings.py)和部署文件(requirements.txt)
Django 1.6 最佳实践: 如何设置django项目的设置(settings.py)和部署文件(requirements.txt) 作者: Desmond Chen,发布日期: 2014-05- ...
- 在objc项目中使用常量的最佳实践
在objc项目中使用常量的最佳实践 之前,在在objc项目中使用常量中,使用c的预处理#define来设置常量.比如,可以做个头文件,然后在需要的类文件中import,使用常量. 但这不是最佳实践 ...
- SpringBoot系列: Spring项目异常处理最佳实践
===================================自定义异常类===================================稍具规模的项目, 一般都要自定义一组异常类, 这 ...
- nodejs 实践:express 最佳实践 (一) 项目结构
express 最佳实践 (一) 第二篇: express 最佳实践(二):中间件 最近,一直在使用 nodejs 做项目,对 nodejs 开发可以说深有体会. 先说说 nodejs 在业务中的脚色 ...
- go项目dockerfile最佳实践
1. 前言 2. 不需要cgo情况下的最佳实践 3. 依赖cgo情况下的最佳实践 1. 前言 这几天在构建golang编写的web项目中,关于dockerfile编写的一些总结 可能是单纯我比较菜(大 ...
随机推荐
- 【Java】学习路径34-文件IO练习题
练习题: 1.检测某目录(scr目录为例)下java源程序文件的数量. 参考思路: 首先获取到scr目录,然后使用list()获取所有名字,再使用String类下的endsWith方法检查即可. 参考 ...
- KingbaseES 数据库参数优化
一.数据库应用类型 针对不同的应用模型,需要对数据库配置进行优化: 1.网络应用程序(WEB) 通常受 CPU 限制 DB比RAM小得多 90% 或更多的简单查询 2.在线事务处理 (OLTP) ...
- KingbaseES R6 集群 recovery 参数对切换的影响
案例说明:在KingbaseES R6集群中,主库节点出现宕机(如重启或关机),会产生主备切换,但是当主库节点系统恢复正常后,如何对原主库节点进行处理,保证集群数据的一致性和安全,可以通过对repmg ...
- 引擎之旅 前传:C++代码规范
自己以前写代码时,一个项目一个风格.单人开发的工作使得我并没有注意到代码规范性和可读性的问题.每当项目结束后,看到自己杂乱无章的代码,完全没有二次开发和重构的欲望. 写代码就应该像写诗一样优雅. by ...
- Gitea 1.17.1 正式发布 | 08 累积更新
Gitea 1.17.1 已正式发布.在这个小的版本更新中我们合并了 35 个 PR,没有包含功能性的更改,但我们强烈建议用户升级到此版本以获得重要的修复补丁. 致谢:感谢报告问题的安全研究人员,同时 ...
- AD画板从头开始
AD画板从头开始 前言 近期认真的画了一次板子,以前虽然也画过,但是都是很随意的,这次是做一个小项目,然后因为有一段时间没有画板了,发现自己很多基础的东西都忘记了,这里就来记录一下从头到尾的过程.本次 ...
- 小程序 AI/AR 能力
一.关于 VisionKit 1.定义 VisionKit 为小程序提供了开发 AR 功能的能力,包含了 AR 在内的视觉算法. 2.版本 提供了 V1 和 V2 两个版本,区别如下: V1平面接口, ...
- 使用elasticdump迁移es数据
安装elasticdump github地址:https://github.com/elasticsearch-dump/elasticsearch-dump # yum -y install npm ...
- MySQL数据表更新模板
-- ---------------------------- -- 新增表 -- ---------------------------- CREATE TABLE `biz_circle_lead ...
- Fluentd部署详解
Fluentd系统配置项 https://www.cnblogs.com/sanduzxcvbnm/p/13920972.html Fluentd自身日志 https://www.cnblogs.co ...