用户与角色 Springboot + Security + Mybatis

用户与角色 Springboot + Security + Mybatis

安全认证与授权包括基于内存的认证,基于数据库的认证,高级配置
基于内存的认证是直接将用户名,密码,和用户与角色的url授权信息直接写在配置文件中,不需要读取数据库;
基于数据库的认证则是从数据库中直接读取用户名和密码,用户与角色的url授权信息仍然写在配置文件中;
高级配置中将这三条信息都存在数据库表中,为的是实现动态配置URL权限。
配置Security重点在于WebSecurityConfigurerAdapter
本文代码参考《Spring Boot + Vue 全栈开发实战》王松 第十章


基于内存的认证

  1. 添加security依赖
 		<dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-security</artifactId>
        </dependency>
  1. 自定义WebSecurityConfigurerAdapter
@Configuration
public class MyWebSecurityConfig extends WebSecurityConfigurerAdapter {
    @Bean
    PasswordEncoder passwordEncoder(){
        return new BCryptPasswordEncoder(10);//密码加密方式,BCrypt强哈希函数
    }
    @Override//配置三名用户的的角色和密码,密码明文全是123
    protected void configure(AuthenticationManagerBuilder auth) throws Exception {
        auth.inMemoryAuthentication()
                .withUser("root").password("$2a$10$yKbvsbp.mwbBmon34q3k/uZdXWbsh/8tDkChKqAbq/3xm7xC2VCTi").roles("ADMIN","DBA")
                .and()
                .withUser("admin").password("$2a$10$bvW.9j56BG0XPAA8icJQhuU4qUS7zT.lCayiqBry45HqJMajVV2JC").roles("ADMIN","USER")
                .and()
                .withUser("zhsh").password("$2a$10$UCu/YkcMgyvbumqM8unEeOSpDtCVAmCG0pncWS6CxcYSIGpQCcBtm").roles("USER");
    }
    @Override
    protected void configure(HttpSecurity http) throws Exception {
        http.authorizeRequests()
                .antMatchers("/admin/**").hasRole("ADMIN")//配置URL的角色要求
                .antMatchers("/user/**").access("hasAnyRole('ADMIN','USER')")
                .antMatchers("/db/**").access("hasRole('ADMIN') and hasRole('DBA')")
                .anyRequest().authenticated()
                .and()
                .formLogin().loginProcessingUrl("/login")//开启表单登录
                .usernameParameter("name").passwordParameter("passwd")
                .successHandler(new AuthenticationSuccessHandler() {//登录成功的一段JSON提示
                    @Override
                    public void onAuthenticationSuccess(HttpServletRequest req, HttpServletResponse resp,
                                                        Authentication auth) throws IOException {
                        Object principal = auth.getPrincipal();
                        resp.setContentType("application/json;charset=utf-8");
                        PrintWriter out = resp.getWriter();
                        resp.setStatus(200);
                        Map<String,Object> map = new HashMap<>();
                        map.put("status",200);
                        map.put("msg",principal);
                        ObjectMapper om = new ObjectMapper();
                        out.write(om.writeValueAsString(map));
                        out.flush();
                        out.close();
                    }
                })
                .failureHandler(new AuthenticationFailureHandler() {//登录失败的一段JSON提示
                    @Override
                    public void onAuthenticationFailure(HttpServletRequest req, HttpServletResponse resp,
                                                        AuthenticationException e) throws IOException {
                        resp.setContentType("application/json;charset=utf-8");
                        PrintWriter out = resp.getWriter();
                        resp.setStatus(401);
                        Map<String,Object> map = new HashMap<>();
                        map.put("status",401);
                        if(e instanceof LockedException){
                            map.put("msg","账号被锁定,登录失败!");
                        }else if(e instanceof BadCredentialsException){
                            map.put("msg","账户名或密码输入错误,登陆失败!");
                        }else if(e instanceof DisabledException){
                            map.put("msg","账号被禁用,登陆失败!");
                        }else if(e instanceof AccountExpiredException){
                            map.put("msg","账户已过期,登陆失败!");
                        }else if(e instanceof CredentialsExpiredException){
                            map.put("msg","密码已过期,登陆失败!");
                        }else{
                            map.put("msg","登录失败!");
                        }
                        ObjectMapper om = new ObjectMapper();
                        out.write(om.writeValueAsString(map));
                        out.flush();
                        out.close();
                    }
                })
                .permitAll()
                .and()
                .logout().logoutUrl("/logout")//开启注销登录
                .clearAuthentication(true).invalidateHttpSession(true)
                .addLogoutHandler(new LogoutHandler() {
                    @Override
                    public void logout(HttpServletRequest req, HttpServletResponse resp, Authentication auth) { }
                })
                .logoutSuccessHandler(new LogoutSuccessHandler() {//注销成功后的业务逻辑
                    @Override
                    public void onLogoutSuccess(HttpServletRequest req, HttpServletResponse resp,
                                                Authentication auth) throws IOException {
                        resp.sendRedirect("/loginPage");
                    }
                })
                .and()
                .csrf().disable();//关闭csrf,跨站请求伪造
    }
}
  1. 编写controller进行授权测试
@RestController
public class HelloController {
    @GetMapping("/hello")
    public String hello(){
        return "Hello!";
    }
    @GetMapping("/admin/hello")
    public String admin(){
        return "hello admin";
    }
    @GetMapping("/user/hello")
    public String user(){
        return "hello user";
    }
    @GetMapping("/db/hello")
    public String dba(){
        return "hello dba";
    }
    @GetMapping("/loginPage")
    public ModelAndView loginPage(){
        ModelAndView mv = new ModelAndView("index");
        return mv;
    }
}

基于数据库的认证

  1. 设计数据表
    user,role,user_role三张表
  2. 创建实体类
//因为UserDetails自带Boolean类型的isEnabled函数,会与@Data中的getEnabled有冲突,所以不能使用@Data
public class User implements UserDetails {
    private Integer id;
    private String username;
    private String password;
    private Boolean enabled;
    private Boolean locked;
    private List<Role> roles;

    @Override
    public Collection<? extends GrantedAuthority> getAuthorities() {
        List<SimpleGrantedAuthority> authorities = new ArrayList<>();
        for (Role role: roles){
            authorities.add(new SimpleGrantedAuthority(role.getName()));
        }
        return authorities;
    }

    @Override
    public String getPassword() {return password;}

    @Override
    public String getUsername() {  return username;  }

    @Override
    public boolean isAccountNonExpired() {  return true;  }

    @Override
    public boolean isAccountNonLocked() { return !locked;  }

    @Override
    public boolean isCredentialsNonExpired() {return true;  }

    @Override
    public boolean isEnabled() { return enabled; }
    public void setEnabled(Boolean enabled) { this.enabled = enabled;    }
    public Integer getId() {  return id; }
    public void setId(Integer id) { this.id = id;  }
    public void setUsername(String username) {  this.username = username;    }
    public void setPassword(String password) { this.password = password;    }
    public Boolean getLocked() {  return locked;    }
    public void setLocked(Boolean locked) {this.locked = locked;    }
    public List<Role> getRoles() {  return roles;    }
    public void setRoles(List<Role> roles) {  this.roles = roles;    }
}


@Data
public class Role {
    private Integer id;
    private String name;
    private String nameZh;
}
  1. 添加依赖与配置
   <!--添加MyBatis依赖-->
        <dependency>
            <groupId>org.mybatis.spring.boot</groupId>
            <artifactId>mybatis-spring-boot-starter</artifactId>
            <version>1.3.1</version>
        </dependency>
        <!--添加mysql依赖-->
        <dependency>
            <groupId>mysql</groupId>
            <artifactId>mysql-connector-java</artifactId>
        </dependency>
        <!--添加数据库连接池-->
        <dependency>
            <groupId>com.alibaba</groupId>
            <artifactId>druid</artifactId>
            <version>1.1.10</version>
        </dependency>

######## 数据库配置 ########
spring.datasource.type=com.alibaba.druid.pool.DruidDataSource
spring.datasource.url=jdbc:mysql://localhost:3306/test?serverTimezone=UTC
spring.datasource.username=root
spring.datasource.password=123
spring.datasource.tomcat.max-idle=10
spring.datasource.tomcat.max-active=50
spring.datasource.tomcat.max-wait=10000
spring.datasource.tomcat.initial-size=5
# 采用隔离级别为读写提交
spring.datasource.tomcat.default-transaction-isolation=2

######### MyBatis配置 ########
# 映射文件
mybatis.mapper-locations=classpath:com/mhr/mhr/security/mapper/*.xml
  1. 创建dao与mapper
//dao
@Mapper
public interface UserMapper {
    User loadUserByUsername(String username);
    List<Role> getUserRolesByUid(Integer id);
}

//mapper
<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE mapper  PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
        "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.mhr.mhr.security.dao.UserMapper">
    <select id="loadUserByUsername" resultType="com.mhr.mhr.security.pojo.User">
        select * from user where username=#{username}
    </select>
    <select id="getUserRolesByUid" resultType="com.mhr.mhr.security.pojo.Role">
        select * from role r,user_role ur where r.id=ur.rid and ur.uid=#{id}
    </select>
</mapper>
  1. 创建service与controller
@Service
public class UserService implements UserDetailsService {
    @Autowired
    UserMapper userMapper;

    @Override
    public UserDetails loadUserByUsername(String s) throws UsernameNotFoundException {
        User user = userMapper.loadUserByUsername(s);
        if (user==null){
            throw new UsernameNotFoundException("账户不存在!");
        }
        user.setRoles(userMapper.getUserRolesByUid(user.getId()));
        return user;
    }
}

@RestController
public class UserController {
    @Autowired
    UserService userService;
    @GetMapping("/loadUserByUsername")
    public User loadUserByUsername(String username){
        return (User) userService.loadUserByUsername(username);
    }
}
  1. 配置Spring Security
@Configuration
public class WebSecurityConfig2 extends WebSecurityConfigurerAdapter {
    @Autowired
    UserService userService;

    @Bean
    PasswordEncoder passwordEncoder(){
        return new BCryptPasswordEncoder();
    }
    @Override	//将UserService配置到AuthenticationManagerBuilder中,没有配置内存用户
    protected void configure(AuthenticationManagerBuilder auth) throws Exception {
        auth.userDetailsService(userService);
    }
    @Override
    protected void configure(HttpSecurity http) throws Exception {
        http.authorizeRequests()
                .antMatchers("/admin/**").hasRole("admin")
                .antMatchers("/user/**").hasRole("dba")
                .antMatchers("/db/**").hasRole("user")
                .anyRequest().authenticated()
                .and()
                .formLogin().loginProcessingUrl("/login").permitAll()
                .and()
                .csrf().disable();
    }
}

高级配置

动态配置url权限

  1. 添加数据表
    menu,menu_role
  2. 编写dao和mapper
//dao
@Mapper
public interface MenuMapper {
    List<Menu> getAllMenus();
}
//mapper
<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE mapper  PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
        "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.mhr.mhr.security.dao.MenuMapper">
    <resultMap id="BaseResultMap" type="com.mhr.mhr.security.pojo.Menu">
        <id property="id" column="id"/>
        <result property="pattern" column="pattern"/>
        <collection property="roles" ofType="com.mhr.mhr.security.pojo.Role">
            <id property="id" column="id"/>
            <result property="name" column="rname"/>
            <result property="nameZh" column="rnameZh"/>
        </collection>
    </resultMap>
    <select id="getAllMenus" resultMap="BaseResultMap">
        select m.*,r.id as rid,r.name as rname,r.nameZh as rnameZh
        from menu m
        left join menu_role mr on m.`id`=mr.`mid`
        left join role r on mr.`rid`=r.`id`
    </select>
</mapper>
  1. 自定义FilterInvocationSecurityMetadataSource
//主要实现接口中的getAttributes方法
@Component
public class CustomFilterInvocationSecurityMetadataSource implements FilterInvocationSecurityMetadataSource {
    @Autowired
    MenuMapper menuMapper;
    AntPathMatcher antPathMatcher = new AntPathMatcher();
    
    @Override
    public Collection<ConfigAttribute> getAttributes(Object o) throws IllegalArgumentException {
        String requestUrl = ((FilterInvocation)o).getRequestUrl();
        List<Menu> allMenus = menuMapper.getAllMenus();
        for (Menu menu: allMenus){
            if(antPathMatcher.match(menu.getPattern(),requestUrl)){	//ant风格的url匹配
                List<Role> roles = menu.getRoles();
                String[] roleArr = new String[roles.size()];
                for (int i = 0; i < roleArr.length; i++){
                    roleArr[i] = roles.get(i).getName();
                }
                return SecurityConfig.createList(roleArr);
            }
        }
        return SecurityConfig.createList("ROLE_LOGIN");
    }
    @Override
    public Collection<ConfigAttribute> getAllConfigAttributes() {   return null;    }
    @Override
    public boolean supports(Class<?> aClass) {  return FilterInvocation.class.isAssignableFrom(aClass);    }
}
  1. 自定义AccessDecisionManager
// 进行角色信息的比对
@Component
public class CustomAccessDecisionManager implements AccessDecisionManager {
    @Override
    public void decide(Authentication auth, Object o, Collection<ConfigAttribute> ca){
        Collection<? extends GrantedAuthority> auths = auth.getAuthorities();
        for (ConfigAttribute configAttribute : ca){
            if("ROLE_LOGIN".equals(configAttribute.getAttribute())
                    && auth instanceof UsernamePasswordAuthenticationToken){
                return;
            }
            for (GrantedAuthority authority: auths){
                if (configAttribute.getAttribute().equals(authority.getAuthority())){
                    return;
                }
            }
        }
        throw new AccessDeniedException("权限不足!");
    }
    @Override
    public boolean supports(ConfigAttribute configAttribute) {  return true;    }
    @Override
    public boolean supports(Class<?> aClass) {return true;    }
}
  1. 配置Spring Security
@Configuration
public class WebSecurityConfig3 extends WebSecurityConfigurerAdapter {
    @Bean
    PasswordEncoder passwordEncoder(){
        return new BCryptPasswordEncoder();
    }
    @Override
    protected void configure(HttpSecurity http) throws Exception {
        http.authorizeRequests()
                .withObjectPostProcessor(new ObjectPostProcessor<FilterSecurityInterceptor>() {
                    @Override
                    public <O extends FilterSecurityInterceptor> O postProcess(O o) {
                        o.setSecurityMetadataSource(cfisms());
                        o.setAccessDecisionManager(cadm());
                        return o;
                    }
                })
                .and()
                .formLogin().loginProcessingUrl("/login").permitAll()
                .and()
                .csrf().disable();
    }
    @Bean
    CustomFilterInvocationSecurityMetadataSource cfisms(){  return new CustomFilterInvocationSecurityMetadataSource();    }
    @Bean
    CustomAccessDecisionManager cadm(){  return new CustomAccessDecisionManager();    }
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值