脚本宝典收集整理的这篇文章主要介绍了

基于springboot的security机制(自定义登录页面+基于内存身份认证+基于mybatis身份认证)

脚本宝典小编觉得挺不错的,现在分享给大家,也给大家做个参考,希望能帮助你少写一行代码,多一份安全和惬意。

接着上一章节,我们在这一章种讨论如何在现有的ssm框架中加入security机制,说白了,就是为我们项目提供身份验证的功能。现有的需求中大多项目都无法脱离登录注册功能。如果开发时每个模块提供一个登录注册功能,整个项目就会臃肿不堪,单点登录也就应用而生了。至于OAuth2与springBoot的结合我们在随后章节讨论,这一章节讨论security机制的简单应用。

In-Memory Authentication

基于内存的身份认证功能。也就是说身份信息是保存到内存中。这种方式了解为主,在实际开发中使用较少。

1 搭建ssm+springsecurity框架

需要的依赖有

- web(spring mvc), - mybatis(mybatis数据库), - mysql(mysql数据库驱动), - security(安全校验机制)
   > spring init -g=com.briup.apps -a=app04 -p=war -d=web,mybatis,mysql,security app04    > cd app04    > mvn install

构建项目过程中依旧会报没有指定驱动类的异常,解决方案还是按照上一章节的方式,在application.properties中进行配置,然后在pom.xml中配置热部署的依赖(方便开发)

配置就绪后启动项目

    > mvn spring-boot:run

clipboard.png

哈,是不是有些意外,我们就没做什么事情,竟然具有授权的功能了,这是security默认帮我们实现的功能,那么用户名密码是什么呢? 用户名默认为user,密码在启动项目的时候会打印到控制台。

clipboard.png

如果我们直接点击取消,提示未授权异常。

clipboard.png

刷新页面后进行登录。输入user/console中密码,出现如下错误,不过这个错误我们是能理解的,404找不到,说明没有配置服务。

clipboard.png

2 自定义授权

在默认授权管理中如果我们想添加用户改怎么办?如果我们想自定义登录页面怎么办?如果我们想自定义拦截怎么办?

2.1 添加自定义用户

实际上我们项目之所有具有授权功能,是security框架帮我们实现的。也就是WebSecurityConfigurerAdapter这个适配器完成,如果想要改变其默认行为,那可以重写该适配器中的一些方法。

/**  * 自定义身份验证类(用于重写WebSecurityConfigurerAdapter默认配置)  * @Configuration     表示这是一个配置类  * @EnableWebSecurity    允许security  * configure()     该方法重写了父类的方法,用于添加用户与角色  * */ @Configuration @EnableWebSecurity public class AuthConfig extends WebSecurityConfigurerAdapter {          /**      * 重写该方法,添加自定义用户      * */     @Override     protected void configure(AuthenticationManagerBuilder auth) throws Exception {         auth.inMemoryAuthentication()         .withUser("admin").password("admin").roles("ADMIN")         .and()         .withUser("terry").password("terry").roles("USER")         .and()         .withUser("larry").password("larry").roles("USER");     } }

重启服务进行测试

clipboard.png

当用户名密码输入错误的时候,出现以下界面

clipboard.png

当用户名密码输入正确的时候,是可以继续访问服务,由于我们还么有提供任何服务,所有均会出现404异常。

2.2 提供服务

订单控制器 OrderController

@RestController @RequestMapping("/orders") public class OrderController {          @GetMapping("/findAll")     public String findAll() {         return "findAll";     }  }

用户管理控制器 UserController

@RestController @RequestMapping("/users") public class UserController {          @GetMapping("/findAll")     public String findAll() {         return "user list";     } }

紧接着在AuthConfig 中配置权限。

@Configuration @EnableWebSecurity public class AuthConfig extends WebSecurityConfigurerAdapter {          /**      * 重写该方法,设定用户访问权限      * 用户身份可以访问 订单相关API      * */     @Override     protected void configure(HttpSecurity http) throws Exception {         http.authorizeRequests()         .antMatchers("/orders/**").hasRole("USER")    //用户权限         .antMatchers("/users/**").hasRole("ADMIN")    //管理员权限         .antMatchers("/login").permitAll()         .and()         .formLogin();                  //super.configure(http);     }      /**      * 重写该方法,添加自定义用户      * */     @Override     protected void configure(AuthenticationManagerBuilder auth) throws Exception {         auth.inMemoryAuthentication()         .withUser("admin").password("admin").roles("ADMIN","USER")         .and()         .withUser("terry").password("terry").roles("USER")         .and()         .withUser("larry").password("larry").roles("USER");     }      }

重启服务进行登录

  • 如果使用admin账号登录则users相关API和orders相关API都可以访问
  • 如果使用terry账号登录则只能访问orders相关API

clipboard.png

3 自定义登录页面

默认情况下,当用户没有登录就去访问受保护资源时,系统会默认请求/login(get方式),这时重定向到登录页(spring security自带)。当输入用户名密码点击登录按钮的时候,系统会请求/login(post方式)。现在我们希望自定义登录页面(默认的登录页面很丑),但是身份校验还是希望由security来进行。这时候我们只需要将登录页面重定向到我们自定义页面即可,这时候DIY表单,但是在这里切记一点。登录页面重定向的地址和表单提交的地址务必一致!

  • 自定义配置 AuthConfig

在原来的基础上扩展了DIY登录页面的控制器的设置

/**  * 自定义身份验证类(用于重写WebSecurityConfigurerAdapter默认配置)  * @Configuration     表示这是一个配置类  * @EnableWebSecurity    允许security  * configure()     该方法重写了父类的方法,用于添加用户与角色  * */ @Configuration @EnableWebSecurity public class AuthConfig extends WebSecurityConfigurerAdapter {          /**      * 重写该方法,设定用户访问权限      * 用户身份可以访问 订单相关API      * */     @Override     protected void configure(HttpSecurity http) throws Exception {         http.authorizeRequests()         .antMatchers("/orders/**").hasRole("USER")    //用户权限         .antMatchers("/users/**").hasRole("ADMIN")    //管理员权限         .and()         .formLogin()         .loginPage("/login")    //跳转登录页面的控制器,该地址要保证和表单提交的地址一致!         .permitAll()         .and()         .logout()         .permitAll()         .and()         .csrf().disable();        //暂时禁用CSRF,否则无法提交表单     }      /**      * 重写该方法,添加自定义用户      * */     @Override     protected void configure(AuthenticationManagerBuilder auth) throws Exception {         auth.inMemoryAuthentication()         .withUser("admin").password("admin").roles("ADMIN","USER")         .and()         .withUser("terry").password("terry").roles("USER")         .and()         .withUser("larry").password("larry").roles("USER");     }      }
  • 添加 login(get方式)控制器

即如果用户没有登录就访问受保护的资源,系统将会进行拦截,拦截之后会请求/login(get方式),然后经过我们这个控制器跳转到DIY登录页面。

@Controller @RequestMapping("/") public class IndexController {      @GetMapping("/login")     public String login(Model model, @RequestParam(value = "error", required = false) String error) {         if (error != null) {             model.addAttribute("error", "用户名或密码错误");         }         return "forward:/login_page.html";     } } 
  • 登录页面 (login_page.html)

注意:这里表单的action为 /login 提交方式为POST

<!DOCTYPE html> <html lang="en"> <head>     <meta charset="UTF-8">     <title>登录页面</title> </head> <body> <h2>自定义登录页面</h2> <hr> <form action="/login" method="POST" name="f">     用户名<input type="text" name="username"/> <br>     密码 <input type="password" name="password"> <br>     <input type="submit" value="登录"> </form> </body> </html>

当需要登录的时候,会跳转到login_page.html中,至此完成自定义登录页面设置

clipboard.png

4 登录后续操作

这里我只是简单处理了一下,通过SecurityContextHolder获取目前登录的用户信息,然后将其放到session中(不建议如此处理)然后将页面重定向到首页中。

@Configuration @EnableWebSecurity public class AuthConfig extends WebSecurityConfigurerAdapter {          /**      * 重写该方法,设定用户访问权限      * 用户身份可以访问 订单相关API      * */     @Override     protected void configure(HttpSecurity http) throws Exception {         http.authorizeRequests()         .antMatchers("/orders/**").hasRole("USER")    //用户权限         .antMatchers("/users/**").hasRole("ADMIN")    //管理员权限         .and()         .formLogin()         .loginPage("/login")    //跳转登录页面的控制器,该地址要保证和表单提交的地址一致!         .successHandler(new AuthenticationSuccessHandler() {             @Override             public void onAuthenticationSuccess(HttpServletRequest arg0, HttpServletResponse arg1, Authentication arg2)                     throws IOException, ServletException {                 Object principal = SecurityContextHolder.getContext().getAuthentication().getPrincipal();                 if (principal != null && principal instanceof UserDetails) {                     UserDetails user = (UserDetails) principal;                     System.out.println("loginUser:"+user.getUsername());                     //维护在session中                     arg0.getSession().setAttribute("userDetail", user);                     arg1.sendRedirect("/");                 }              }         })         .permitAll()         .and()         .logout()         .permitAll()         .and()         .csrf().disable();        //暂时禁用CSRF,否则无法提交表单     }      /**      * 重写该方法,添加自定义用户      * */     @Override     protected void configure(AuthenticationManagerBuilder auth) throws Exception {         auth.inMemoryAuthentication()         .withUser("admin").password("admin").roles("ADMIN","USER")         .and()         .withUser("terry").password("terry").roles("USER")         .and()         .withUser("larry").password("larry").roles("USER");     }      }

Mybatis Authentication

数据库认证。也就是说要提供数据库的支持,用户信息和角色统一保存到数据库中,这样后期可以提供注册功能向数据库中添加用户信息。

1. 数据库设计

设计了三张表,用户表,角色表,用户角色表,用户与角色之前是多对多关系。外键维护在桥表中。
clipboard.png

建表语句如下

SET FOREIGN_KEY_CHECKS=0;  -- ---------------------------- -- Table structure for tbl_role -- ---------------------------- DROP TABLE IF EXISTS `tbl_role`; CREATE TABLE `tbl_role` (   `id` int(11) NOT NULL AUTO_INCREMENT,   `name` varchar(255) DEFAULT NULL,   PRIMARY KEY (`id`) ) ENGINE=InnoDB AUTO_INCREMENT=4 DEFAULT CHARSET=utf8;  -- ---------------------------- -- Table structure for tbl_user -- ---------------------------- DROP TABLE IF EXISTS `tbl_user`; CREATE TABLE `tbl_user` (   `id` int(11) NOT NULL AUTO_INCREMENT,   `username` varchar(255) DEFAULT NULL,   `password` varchar(255) DEFAULT NULL,   `state` varchar(255) DEFAULT NULL,   `name` varchar(255) DEFAULT NULL,   `gender` varchar(255) DEFAULT NULL,   `birth` varchar(255) DEFAULT NULL,   PRIMARY KEY (`id`) ) ENGINE=InnoDB AUTO_INCREMENT=7 DEFAULT CHARSET=utf8;  -- ---------------------------- -- Table structure for tbl_user_role -- ---------------------------- DROP TABLE IF EXISTS `tbl_user_role`; CREATE TABLE `tbl_user_role` (   `id` int(11) NOT NULL AUTO_INCREMENT,   `user_id` int(11) DEFAULT NULL,   `role_id` int(11) DEFAULT NULL,   PRIMARY KEY (`id`),   KEY `user_id` (`user_id`),   KEY `role_id` (`role_id`),   CONSTRAINT `tbl_user_role_ibfk_2` FOREIGN KEY (`role_id`) REFERENCES `tbl_role` (`id`),   CONSTRAINT `tbl_user_role_ibfk_1` FOREIGN KEY (`user_id`) REFERENCES `tbl_user` (`id`) ) ENGINE=InnoDB AUTO_INCREMENT=6 DEFAULT CHARSET=utf8; 

2. 提供对应的bean mapper service

都是基础代码,这里就不详细列出来。随后提交到github上

clipboard.png

3. 自定义身份验证

3.1 创建UserDetails的实现类

为了使得我们的用户角色类能和security中的能够结合起来,需要重新建一个类MyUserDetails实现UserDetails接口。

MyUserDetails

/**  * 自定义用户身份信息  * */ public class MyUserDetails implements UserDetails {     // 用户信息     private User user;     // 用户角色     private Collection<? extends GrantedAuthority> authorities;          public MyUserDetails(User user, Collection<? extends GrantedAuthority> authorities) {         super();         this.user = user;         this.authorities = authorities;     }      /**      *       */     private static final long serialVersionUID = 1L;      @Override     public Collection<? extends GrantedAuthority> getAuthorities() {                  return authorities;     }      @Override     public String getPassword() {         return this.user.getPassword();     }      @Override     public String getUsername() {         return this.user.getUsername();     }      @Override     public boolean isAccountNonExpired() {         return this.user.getState().equals(User.STATE_ACCOUNTEXPIRED);     }      @Override     public boolean isAccountNonLocked() {         return this.user.getState().equals(User.STATE_LOCK);     }      @Override     public boolean isCredentialsNonExpired() {         return this.user.getState().equals(User.STATE_TOKENEXPIRED);     }      @Override     public boolean isEnabled() {         return this.user.getState().equals(User.STATE_NORMAL);     }  }

用户身份验证 AuthUserDetailService

/**  * 用户身份认证服务类  * */ @Service("userDetailsService") public class AuthUserDetailService implements UserDetailsService {     @Autowired     private UserMapper userMapper;     @Autowired     private UserRoleMapper userRoleMapper;     @Override     public UserDetails loadUserByUsername(String name)              throws UsernameNotFoundException {         UserDetails userDetails = null;         try {             User user = userMapper.findByUsername(name);             if(user != null) {                 List<UserRole> urs = userRoleMapper.findByUserId(user.getId());                 Collection<GrantedAuthority> authorities = new ArrayList<>();                 for(UserRole ur : urs) {                     String roleName = ur.getRole().getName();                     SimpleGrantedAuthority grant = new SimpleGrantedAuthority(roleName);                     authorities.add(grant);                 }                 //封装自定义UserDetails类                 userDetails = new MyUserDetails(user, authorities);             } else {                 throw new UsernameNotFoundException("该用户不存在!");             }         } catch (Exception e) {             e.printStackTrace();         }         return userDetails;     }  }

自定义认证服务

/**  * 自定义认证服务  * */ @Service("securityProvider") public class SecurityProvider implements AuthenticationProvider {     private  UserDetailsService userDetailsService;       public SecurityProvider(UserDetailsService userDetailsService) {           this.userDetailsService = userDetailsService;       }       @Override     public Authentication authenticate(Authentication authenticate) throws AuthenticationException {         UsernamePasswordAuthenticationToken token              = (UsernamePasswordAuthenticationToken) authenticate;         String username = token.getName();         UserDetails userDetails = null;                  if(username !=null) {             userDetails = userDetailsService.loadUserByUsername(username);         }         System.out.println("$$"+userDetails);                  if(userDetails == null) {               throw new UsernameNotFoundException("用户名/密码无效");           }                  else if (!userDetails.isEnabled()){               System.out.println("jinyong用户已被禁用");             throw new DisabledException("用户已被禁用");           }else if (!userDetails.isAccountNonExpired()) {               System.out.println("guoqi账号已过期");             throw new LockedException("账号已过期");           }else if (!userDetails.isAccountNonLocked()) {               System.out.println("suoding账号已被锁定");             throw new LockedException("账号已被锁定");           }else if (!userDetails.isCredentialsNonExpired()) {               System.out.println("pingzheng凭证已过期");             throw new LockedException("凭证已过期");           }                    String password = userDetails.getPassword();          //与authentication里面的credentials相比较           if(!password.equals(token.getCredentials())) {               throw new BadCredentialsException("Invalid username/password");           }           //授权           return new UsernamePasswordAuthenticationToken(userDetails, password,userDetails.getAuthorities());       }      @Override     public boolean supports(Class<?> authentication) {          //返回true后才会执行上面的authenticate方法,这步能确保authentication能正确转换类型           return UsernamePasswordAuthenticationToken.class.equals(authentication);       }  }

核心认证配置

@Configuration @EnableWebSecurity public class AuthConfig extends WebSecurityConfigurerAdapter {     @Autowired     private UserDetailsService userDetailsService;     @Autowired     private AuthenticationProvider securityProvider;          @Override     protected UserDetailsService userDetailsService() {         //自定义用户信息类         return this.userDetailsService;     }          @Override     protected void configure(AuthenticationManagerBuilder auth) throws Exception {         //自定义AuthenticationProvider           auth.authenticationProvider(securityProvider);     }            /**      * 重写该方法,设定用户访问权限      * 用户身份可以访问 订单相关API      * */     @Override     protected void configure(HttpSecurity http) throws Exception {         http.authorizeRequests()         .antMatchers("/orders/**").hasRole("USER")    //用户权限         .antMatchers("/users/**").hasRole("ADMIN")    //管理员权限         .and()         .formLogin()         .loginPage("/login")    //跳转登录页面的控制器,该地址要保证和表单提交的地址一致!         //成功处理         .successHandler(new AuthenticationSuccessHandler() {             @Override             public void onAuthenticationSuccess(HttpServletRequest arg0, HttpServletResponse arg1, Authentication arg2)                     throws IOException, ServletException {                 Object principal = SecurityContextHolder.getContext().getAuthentication().getPrincipal();                 if (principal != null && principal instanceof UserDetails) {                     UserDetails user = (UserDetails) principal;                     System.out.println("loginUser:"+user.getUsername());                     //维护在session中                     arg0.getSession().setAttribute("userDetail", user);                     arg1.sendRedirect("/");                 }              }         })         //失败处理         .failureHandler(new AuthenticationFailureHandler() {                          @Override             public void onAuthenticationFailure(HttpServletRequest request, HttpServletResponse response, AuthenticationException authenticationException)                     throws IOException, ServletException {                 System.out.println("error"+authenticationException.getMessage());                 response.sendRedirect("/login");             }         })         .permitAll()         .and()         .logout()         .permitAll()         .and()         .csrf().disable();        //暂时禁用CSRF,否则无法提交表单     }      }

这时候就可以准备通过数据库用户进行登录。

用户访问资源-》security拦截-》跳转到login-》提交表单-》securityProvider处理用户信息-》借助UserDetailsService 获取用户信息-》认证成功/失败

代码地址

总结

以上是脚本宝典为你收集整理的

基于springboot的security机制(自定义登录页面+基于内存身份认证+基于mybatis身份认证)

全部内容,希望文章能够帮你解决

基于springboot的security机制(自定义登录页面+基于内存身份认证+基于mybatis身份认证)

所遇到的程序开发问题,欢迎加入QQ群277859234一起讨论学习。如果觉得脚本宝典网站内容还不错,欢迎将脚本宝典网站推荐给程序员好友。 本图文内容来源于网友网络收集整理提供,作为学习参考使用,版权属于原作者。如您有任何意见或建议可联系处理。小编QQ:384754419,请注明来意。

80%的人都看过