基于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 获取用户信息-》认证成功/失败

代码地址

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
好的,基于SpringBootMyBatis和Layui的项目技术介绍如下: 1. SpringBootSpringBoot是一个基于Spring框架的快速开发脚手架,它可以帮助我们快速搭建项目环境、自动配置相关依赖、简化开发流程等。在SpringBoot中,我们可以使用注解、自动配置、启动器等方式来快速搭建项目,并且提供了很多常用的功能模块,如Web、数据访问、安全、缓存等。 2. MyBatisMyBatis是一个Java持久层框架,它可以帮助我们简化数据库操作,提高开发效率。MyBatis通过XML或注解的方式来配置SQL语句,可以灵活地处理各种复杂的SQL场景,并且提供了很多内置的CRUD操作,如增删改查、批量操作、分页等。 3. Layui:Layui是一个轻量级的前端UI框架,它可以帮助我们快速搭建美观、易用的前端页面。Layui提供了很多常用的组件,如表格、表单、弹出层、树形结构等,同时还提供了很多主题、字体、图标等资源,可以自定义样式。 基于这些技术,我们可以开发出一个基于Web的管理系统,实现用户登录、权限管理、数据展示等功能。具体实现方式可以参考以下步骤: 1. 配置SpringBoot环境,引入相关的依赖,如SpringBootMyBatis、Lombok等。 2. 配置MyBatis,使用XML或注解的方式编写SQL语句,配置数据源、事务管理等。 3. 编写实体类、Mapper接口、Service接口和实现类,实现数据访问和业务逻辑。 4. 使用Layui编写前端页面,实现用户登录、权限管理、数据展示等功能。 5. 集成SpringSecurity实现安全认证和授权,保护系统数据安全。 6. 集成Redis实现缓存,提高系统性能和用户体验。 以上是基于SpringBootMyBatis和Layui的项目技术介绍,希望对您有所帮助。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值