👍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

&#128077;SpringSecurity单体项目最佳实践

  • 🅱️ 回到浏览器,输入http://localhost:8080/community,由于我们此时还未登陆,会重定向到默认创建的登陆页面中,这是SpringSecurity默认为我们做的。
  • 🕐 输入控制台的密码,即可进入到系统,

&#128077;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接口

&#128077;SpringSecurity单体项目最佳实践

  • ⭕️ 它肯定也有默认实现类的,但是我们需要查询数据库对应的用户数据,所以我们还是采用自定义的方式去完成。
@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就已经完结啦,文章中可能有些地方不能做到全面覆盖,视频教程地址

发表评论

相关文章