Spring boot+Spring security实现前后端分离登录认证及动态权限控制

一.引入相关依赖

<!--JDBC-->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-jdbc</artifactId>
        </dependency>

        <!--spring security  引入此依赖后项目中的所有接口就会自动会保护起来(需要登录后才能访问)-->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-security</artifactId>
        </dependency>

        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>

        <!--mybaits-->
        <dependency>
            <groupId>org.mybatis.spring.boot</groupId>
            <artifactId>mybatis-spring-boot-starter</artifactId>
            <version>2.1.3</version>
        </dependency>

        <!--mysql驱动-->
        <dependency>
            <groupId>mysql</groupId>
            <artifactId>mysql-connector-java</artifactId>
            <scope>runtime</scope>
            <version>5.1.47</version>
        </dependency>

        <!--druid连接池-->
        <dependency>
            <groupId>com.alibaba</groupId>
            <artifactId>druid-spring-boot-starter</artifactId>
            <version>1.1.10</version>
        </dependency>
        
		<!--spring boot 测试依赖-->
 		<dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-test</artifactId>
            <scope>test</scope>
        </dependency>

二. 用户实体类编写

/*
 * @className: Hr
 * @description 用户类,实习UserDetails接口
 * @since JDK1.8
 * @author ljh
 * @createdAt  2020/7/17 0017 
 * @version 1.0.0
 **/
public class Hr implements UserDetails{
    private Integer id;

    private String name;

    private String phone;

    private String telephone;

    private String address;

    private Boolean enabled;

    private String username;

    private String password;

    private String userface;

    private String remark;

    public Integer getId() {
        return id;
    }

    public void setId(Integer id) {
        this.id = id;
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name == null ? null : name.trim();
    }

    public String getPhone() {
        return phone;
    }

    public void setPhone(String phone) {
        this.phone = phone == null ? null : phone.trim();
    }

    public String getTelephone() {
        return telephone;
    }

    public void setTelephone(String telephone) {
        this.telephone = telephone == null ? null : telephone.trim();
    }

    public String getAddress() {
        return address;
    }

    public void setAddress(String address) {
        this.address = address == null ? null : address.trim();
    }

    public void setEnabled(Boolean enabled) {
        this.enabled = enabled;
    }

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

    /*账号是否没过期*/
    @Override
    public boolean isAccountNonExpired() {
        return true;
    }

    /*账号是否没过期*/
    @Override
    public boolean isAccountNonLocked() {
        return true;
    }

    /*密码是否没过期*/
    @Override
    public boolean isCredentialsNonExpired() {
        return true;
    }

    /*是否启用*/
    @Override
    public boolean isEnabled() {
        return enabled;
    }

    public void setUsername(String username) {
        this.username = username == null ? null : username.trim();
    }

    /*当前用户权限集合*/
    @Override
    public Collection<? extends GrantedAuthority> getAuthorities() {
        List<SimpleGrantedAuthority> list = new ArrayList<>(roles.size());
        for (Role role : roles) {
            list.add(new SimpleGrantedAuthority(role.getName()));
        }

        return list;
    }

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

    public void setPassword(String password) {
        this.password = password == null ? null : password.trim();
    }

    public String getUserface() {
        return userface;
    }

    public void setUserface(String userface) {
        this.userface = userface == null ? null : userface.trim();
    }

    public String getRemark() {
        return remark;
    }

    public void setRemark(String remark) {
        this.remark = remark == null ? null : remark.trim();
    }
}

三. 资源菜单(权限)实体类

@Data
@AllArgsConstructor
@NoArgsConstructor
@ToString
public class Menu {
    private Integer id;

    private String url;

    private String path;

    private String component;

    private String name;

    private String iconCls;

    private Integer parentId;

    private Boolean enabled;

    private Meta meta;

    private List<Menu> children; //子菜单权限

    private List<Role> roles; //当前权限所要具备的角色
}

四. 角色实体类

@Data
@AllArgsConstructor
@NoArgsConstructor
public class Role {
    private Integer id; //角色ID

    private String name; //角色名称

    private String nameZh; //角色中文名称
}

五. 用户service实现类

/*
 * @className: UserServiceImpl
 * @description 登录服务
 * @since JDK1.8
 * @author ljh
 * @createdAt  2020/7/15 0015
 * @version 1.0.0
 **/
@Service
public class UserServiceImpl implements UserDetailsService {

    @Autowired
    private HrMapper hrMapper;

    /*
     * @metnodName ljh
     * @description 根据用户名获取当前用户信息
     * @param username 
     *  用户名 这里只传入用户名,框架会根据用户名查到用户信息后 然后对前台传入的密码进行加密后和数据库查出的用户密码进行比对,如果一致则登录成功
     * @return UserDetails 用户信息
     * @throws
     * @author
     * @createdAt
     */
    @Override
    public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
               Hr hr = hrMapper.loadUserByUsername(username); 
               // select * from hr where username=#{username}

        if (hr==null){
            throw new UsernameNotFoundException("用户名不存在");
        }
         /*SELECT
            r.id,
            r.`name`,
            r.nameZh
          ROM
            role r
            LEFT JOIN hr_role hrr ON r.id = hrr.rid
            LEFT JOIN hr ON hr.id = hrr.hrid
          HERE
      	    hr.id =#{id}
        * */
        //获取当前用户所具备的角色信息 并设置进hr中
        List<Role> roles = roleMapper.getRoleListByHrId(hr.getId());
        hr.setRoles(roles);
        return hr;
    }
}

六. Security拦截过滤器

/*
 * @className: BeforeFilter
 * @description 所有请求在进系统前过滤判断
 * @since JDK1.8
 * @author ljh
 * @createdAt  2020/7/21 0021
 * @version 1.0.0
 **/
@Component
public class BeforeFilter implements FilterInvocationSecurityMetadataSource {

    @Autowired
    private MenuService menuService;

    AntPathMatcher antPathMatcher = new AntPathMatcher();

    /*
     * @methodName: getAttributes
     * @description 根据访问的接口路径判断当前路径都需要哪些角色才能访问
     * @param:
     * @return:
     * @createdAt 18:23 2020/7/21 0021
     * @version 1.0.0
     **/
    @Override
    public Collection<ConfigAttribute> getAttributes(Object o) throws IllegalArgumentException {

        String url = ((FilterInvocation) o).getRequestUrl(); //访问得到当前的访问接口路径
        /*
        SELECT
      	m.*,
      	r.`name` as rname,
      	r.nameZh
      FROM
      	menu m
      	LEFT JOIN menu_role mr ON m.id = mr.mid
      	LEFT JOIN role r ON r.id = mr.rid
      	ORDER BY m.id
       */
        List<Menu> menus = menuService.getAllRolesByMenus(); //查出所有资源路径所需要的角色
        for (Menu menu : menus) {
            if (antPathMatcher.match(menu.getUrl(), url)) {
                //当前资源路径所需要的角色
                List<Role> roles = menu.getRoles();
                String[] str = new String[roles.size()];
                for (int i = 0; i < roles.size(); i++) {
                    str[i] = roles.get(i).getName();
                }
                return SecurityConfig.createList(str);
            }
        }

        //如果所有的资源都没有匹配到,则设立标记(登录后可以访问)
        return SecurityConfig.createList("ROLE_LOGIN");
    }

    @Override
    public Collection<ConfigAttribute> getAllConfigAttributes() {
        return null;
    }

    @Override
    public boolean supports(Class<?> aClass) {
        return true;
    }
}

七. 访问控制管理

@Component
public class DecisionManager implements AccessDecisionManager {

    /*
     * @methodName: decide
     * @description 判断当前登录用户是否具备访问接口所需角色
     * @param:
     * @return: authentication 当前登录用户
     *          configAttributes 访问的当前接口所需要的角色集合
     * @createdAt 15:29 2020/7/22 0022
     * @version 1.0.0
     **/
    @Override
    public void decide(Authentication auth, Object object, Collection<ConfigAttribute> configAttributes) throws AccessDeniedException, InsufficientAuthenticationException {


        for (ConfigAttribute attribute : configAttributes) {
            //访问该接口所需要的角色
            String needRole = attribute.getAttribute();
            if (needRole.equals("ROLE_LOGIN")){
                if (auth instanceof AnonymousAuthenticationToken){
                    throw new AccessDeniedException("尚未登录,请登录");
                }else {
                    return;
                }
            }

            //当前用户所具备的角色
            Collection<? extends GrantedAuthority> authorities = auth.getAuthorities();
            for (GrantedAuthority authority : authorities) {
                //如果当前用户具备接口所需一种角色 放行 否则抛出异常
                if (authority.getAuthority().equals(needRole)){
                    return;
                }
            }
        }
        throw new AccessDeniedException("权限不足,请联系管理员");
    }

    @Override
    public boolean supports(ConfigAttribute attribute) {
        return true;
    }

    @Override
    public boolean supports(Class<?> clazz) {
        return true;
    }
}

八. security登录授权相关配置类

/*
 * @className: SecurityConfig
 * @description Security登录配置类
 * @since JDK1.8
 * @author ljh
 * @createdAt  2020/7/15 0015
 * @version 1.0.0
 **/
@Configuration
public class SecurityConfig extends WebSecurityConfigurerAdapter {

    @Autowired
    private UserServiceImpl userService;

    @Autowired
    private BeforeFilter beforeFilter;

    @Autowired
    private DecisionManager decisionManager;

    /*
     * @metnodName bCryptPasswordEncoder
     * @description 配置密码加密
     */
    @Bean
    public BCryptPasswordEncoder bCryptPasswordEncoder() {
        return new BCryptPasswordEncoder();  
    }

    /*
     * @metnodName configure
     * @description 配置基于数据库的认证
     */
    @Override
    protected void configure(AuthenticationManagerBuilder auth) throws Exception {
        auth.userDetailsService(userService);
    }

    @Override
    public void configure(WebSecurity web) throws Exception {
        web.ignoring().mvcMatchers("/login"); //对登录接口放行
    }

    @Override
    protected void configure(HttpSecurity http) throws Exception {
        http.authorizeRequests()
            //.anyRequest().authenticated() //任何的请求都要认证
            .withObjectPostProcessor(new ObjectPostProcessor<FilterSecurityInterceptor>() {
                @Override
                public <O extends FilterSecurityInterceptor> O postProcess(O o) {
                    o.setAccessDecisionManager(decisionManager);  //注册自定义拦截器和访问管理器
                    o.setSecurityMetadataSource(beforeFilter);
                    return o;
                }
            })
            .and()
            .formLogin()
            .loginProcessingUrl("/doLogin") //登录的接口url (post请求)
            .loginPage("/login") //跳转的登录页面(这里因为是前后端分离,所以不跳转页面,后端返回json 页面调转交给前端处理)
            .usernameParameter("username") //登录的用户名
            .passwordParameter("password") //登录密码
            //配置登录成功的回调处理器
            .successHandler(new AuthenticationSuccessHandler() {
                @Override
                public void onAuthenticationSuccess(HttpServletRequest req, HttpServletResponse resp,
                                                    Authentication auth) throws IOException, ServletException {
                    resp.setContentType("application/json;charset=utf-8");
                    PrintWriter writer = resp.getWriter();
                    Result result = new Result(ResultCode.SUCCESS);
                    Hr hr = (Hr) auth.getPrincipal();
                    hr.setPassword(null);
                    result.setData(hr);
                    result.setMessage("登录成功");
                    writer.write(new ObjectMapper().writeValueAsString(result));  //登录成功后返回json 前端根据返回的信息进行页面跳转
                    writer.flush();
                    writer.close();
                }
            }).failureHandler(new AuthenticationFailureHandler() {
            //配置失败的回调处理器
            @Override
            public void onAuthenticationFailure(HttpServletRequest req,
                                                HttpServletResponse resp, AuthenticationException e) throws IOException, ServletException {
                resp.setContentType("application/json;charset=utf-8");
                PrintWriter writer = resp.getWriter();
                Result result = new Result(500, null, false);
                if (e instanceof BadCredentialsException) {
                    result.setMessage("用户名或密码错误,请重新输入");
                } else if (e instanceof LockedException) {
                    result.setMessage("账户被锁定,请联系管理员");
                } else if (e instanceof DisabledException) {
                    result.setMessage("账户被禁用,请联系管理员");
                } else if (e instanceof CredentialsExpiredException) {
                    result.setMessage("密码已过期,登录失败");
                } else if (e instanceof AccountExpiredException) {
                    result.setMessage("账户已过期,登录失败");
                }
                writer.write(new ObjectMapper().writeValueAsString(result));
                writer.flush();
                writer.close();
            }
        }).permitAll()
            .and()
            .logout() //注销接口(get请求)
            //注销成功的回调处理器
            .logoutSuccessHandler(new LogoutSuccessHandler() {
                @Override
                public void onLogoutSuccess(HttpServletRequest req,
                                            HttpServletResponse resp, Authentication auth) throws IOException,
                    ServletException {
                    resp.setContentType("application/json;charset=utf-8");
                    PrintWriter writer = resp.getWriter();
                    Result result = new Result(ResultCode.SUCCESS);
                    result.setMessage("注销成功");
                    writer.write(new ObjectMapper().writeValueAsString(result));
                    writer.flush();
                    writer.close();
                }
            }).permitAll()
            .and()
            .csrf().disable()//禁用csrf
            //没有认证时,在这里处理结果,不进行重定向
            .exceptionHandling().authenticationEntryPoint(new AuthenticationEntryPoint() {
            @Override
            public void commence(HttpServletRequest req, HttpServletResponse resp,
                                 AuthenticationException authException) throws IOException, ServletException {
                resp.setContentType("application/json;charset=utf-8");
                PrintWriter writer = resp.getWriter();
                Result result = new Result(500, null, false);
                if (authException instanceof InsufficientAuthenticationException) {
                    result.setMessage("操作异常,请登录后访问");
                }
                writer.write(new ObjectMapper().writeValueAsString(result));
                writer.flush();
                writer.close();
            }
        });

    }
}

九. controller测试接口

@RestController
public class LoginController {

    @RequestMapping("/login")
    public Result login(){
        Result result = new Result(500, "尚未登录,请登录", false);
        return result;
    }

    @RequestMapping("/hello")
    public String hello(){
        return "hello";
    }

    @RequestMapping("/employee/basic")
    public String hello1(){
        return "/emp/basic";
    }

    @RequestMapping("/employee/advanced")
    public String hello2(){
        return "/emp/adv";
    }
}

你可能感兴趣的:(认证,spring,boot,java)