springboot-shiro-springscurity-jwt

版权声明:转载请注明作者 https://blog.csdn.net/myth_g/article/details/84940000

一.shiro

1.realm:这里用了ehcache,其实这里用法时错误的,结合jwt就应该彻底抛弃后台状态(同事写的..)

@Component
public class MyRealm extends AuthorizingRealm {

    @Autowired
    CacheManager cacheManager;

    @Autowired
    UserVo userVo;

    @Override
    protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principalCollection) {
        log.debug("————权限认证————");
        SimpleAuthorizationInfo info = new SimpleAuthorizationInfo();
        info.addRole("user");
        if(userVo.getUserType()== UserType.ADMIN.getType()){
            info.addRole("admin");
        }
        //获得该用户角色
        return info;
    }

    @Override
    public boolean supports(AuthenticationToken token) {
        return token instanceof JWTToken;
    }

    @Override
    protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken authenticationToken) throws AuthenticationException {
        log.debug("————身份认证方法————");
        String token = (String) authenticationToken.getCredentials();
        userVo =  cacheManager.getCache("auth").get(token, UserVo.class);
        if(userVo==null){
            throw new AuthenticationException("token认证失败!");
        }else{
            cacheManager.getCache("auth").put(token,userVo);
        }
        return new SimpleAuthenticationInfo(token, token, "MyRealm");
    }
}

2.filter:

public class JWTFilter extends BasicHttpAuthenticationFilter {

    public final static String AUTH_KEY = "token";
    public final static int NOT_LOGIN = 1;
    public final static int NOT_AUTH  = 2;
    /**
     * 进行token检测
     */
    @Override
    protected boolean isAccessAllowed(ServletRequest request, ServletResponse response, Object mappedValue) {
        //判断请求的请求头是否带上 "Token"
        if (isLoginAttempt(request, response)) {
            //如果存在,则进入 executeLogin 方法执行登入,检查 token 是否正确
            try {
                executeLogin(request, response);
                return true;
            }catch(Exception ex){
                redirect(NOT_LOGIN, response);
            }
            return false;
        }
        redirect(NOT_LOGIN, response);
        return false;
    }

    /**
     * 判断用户是否想要登入。
     * 检测 header 里面是否包含 Token 字段
     */
    @Override
    protected boolean isLoginAttempt(ServletRequest request, ServletResponse response) {
        HttpServletRequest req = (HttpServletRequest) request;
        String token = req.getHeader(AUTH_KEY);
        String requestURI = req.getRequestURI();
        return token != null;
    }

    /**
     * 执行登陆操作
     */
    @Override
    protected boolean executeLogin(ServletRequest request, ServletResponse response) throws AuthenticationException {
        HttpServletRequest httpServletRequest = (HttpServletRequest) request;
        String token = httpServletRequest.getHeader(AUTH_KEY);
        JWTToken jwtToken = new JWTToken(token);
        // 提交给realm进行登入,如果错误他会抛出异常并被捕获
        getSubject(request, response).login(jwtToken);
        // 如果没有抛出异常则代表登入成功,返回true
        return true;
    }

    /**
     * 对跨域提供支持
     */
    @Override
    protected boolean preHandle(ServletRequest request, ServletResponse response) throws Exception {
        HttpServletRequest httpServletRequest = (HttpServletRequest) request;
        HttpServletResponse httpServletResponse = (HttpServletResponse) response;
        httpServletResponse.setHeader("Access-control-Allow-Origin", httpServletRequest.getHeader("Origin"));
        httpServletResponse.setHeader("Access-Control-Allow-Methods", "GET,POST,OPTIONS,PUT,DELETE");
        httpServletResponse.setHeader("Access-Control-Allow-Headers", httpServletRequest.getHeader("Access-Control-Request-Headers"));
        // 跨域时会首先发送一个option请求,这里我们给option请求直接返回正常状态
        if (httpServletRequest.getMethod().equals(RequestMethod.OPTIONS.name())) {
            httpServletResponse.setStatus(HttpStatus.OK.value());
            return false;
        }
        return super.preHandle(request, response);
    }

    private void redirect(int type, ServletResponse response){
        Response response1 = null;
        if(type==NOT_LOGIN){
            response1 = Response.notLogin("未登录");
        }else if(type==NOT_AUTH){
            response1 = Response.notAuth("没有权限");
        }
        try {
            HttpServletResponse httpServletResponse = (HttpServletResponse) response;
            //设置编码,否则中文字符在重定向时会变为空字符串
            httpServletResponse.setContentType("application/json;charset=utf-8");
            httpServletResponse.getWriter().write(JSON.toJSONString(response1));
        } catch (IOException e) {
            log.error(e.getMessage());
        }
    }
}
public class JWTToken implements AuthenticationToken {

    private String token;

    public JWTToken(String token) {
        this.token = token;
    }

    @Override
    public Object getPrincipal() {
        return token;
    }

    @Override
    public Object getCredentials() {
        return token;
    }
}

3.总配置:(这里的过滤器要使用new方式用shiro管理,不能注入spring,否则因为顺序原因导致授权失败)

注意:此处过滤链使用的是linkedMap,保证顺序;

@Configuration
@Slf4j
public class Security {
    

    @Bean
    public ShiroFilterFactoryBean shirFilter(org.apache.shiro.mgt.SecurityManager securityManager) {
        ShiroFilterFactoryBean factoryBean = new ShiroFilterFactoryBean();
        // 添加自己的过滤器并且取名为jwt
        Map<String, Filter> filterMap = new HashMap<>();
        //设置我们自定义的JWT过滤器
        filterMap.put("jwt", new JWTFilter());
        factoryBean.setFilters(filterMap);
        factoryBean.setSecurityManager(securityManager);
        Map<String, String> filterRuleMap = new LinkedHashMap<>();
        // 访问 /unauthorized 不通过JWTFilter
        filterRuleMap.put("/v2/api-docs/**", "anon");
        filterRuleMap.put("/swagger-resources/**", "anon");
        filterRuleMap.put("/swagger-ui.html*", "anon");
        filterRuleMap.put("/webjars/**", "anon");
        filterRuleMap.put("/images/**", "anon");
        filterRuleMap.put("/configuration/**", "anon");
        filterRuleMap.put("/not_login", "anon");
        filterRuleMap.put("/login", "anon");
        // 所有请求通过我们自己的JWT Filter
        filterRuleMap.put("/**", "jwt");
        factoryBean.setFilterChainDefinitionMap(filterRuleMap);
        return factoryBean;
    }

    @Autowired
    MyRealm myRealm;

    /**
     * 注入 securityManager
     */
    @Bean
    public DefaultWebSecurityManager manager(MyRealm realm) {
        DefaultWebSecurityManager manager = new DefaultWebSecurityManager();
        manager.setRealm(realm);
        DefaultSubjectDAO subjectDAO = new DefaultSubjectDAO();
        DefaultSessionStorageEvaluator evaluator = new DefaultSessionStorageEvaluator();
        evaluator.setSessionStorageEnabled(false);
        subjectDAO.setSessionStorageEvaluator(evaluator);
        manager.setSubjectDAO(subjectDAO);
        return manager;
    }

    /**
     * 自定义身份认证 realm;
     * <p>
     * 必须写这个类,并加上 @Bean 注解,目的是注入 CustomRealm,
     * 否则会影响 CustomRealm类 中其他类的依赖注入
     */


    @Bean
    public AuthorizationAttributeSourceAdvisor authorizationAttributeSourceAdvisor(DefaultWebSecurityManager securityManager) {
        AuthorizationAttributeSourceAdvisor advisor = new AuthorizationAttributeSourceAdvisor();
        advisor.setSecurityManager(securityManager);
        return advisor;
    }
    /**
     * 权限注解
     * @return
     */
    @Bean
    @DependsOn("lifecycleBeanPostProcessor")
    public DefaultAdvisorAutoProxyCreator defaultAdvisorAutoProxyCreator() {
        DefaultAdvisorAutoProxyCreator defaultAdvisorAutoProxyCreator = new DefaultAdvisorAutoProxyCreator();
        // 强制使用cglib,防止重复代理和可能引起代理出错的问题
        // https://zhuanlan.zhihu.com/p/29161098
        defaultAdvisorAutoProxyCreator.setProxyTargetClass(true);
        return defaultAdvisorAutoProxyCreator;
    }


    @Bean
    public LifecycleBeanPostProcessor lifecycleBeanPostProcessor() {
        return new LifecycleBeanPostProcessor();
    }
}

注解:

@RequiresRoles("admin")等

二.springscurity

1.userDetails:

@Data
public class MyUserDetails implements UserDetails {

    private String account;
    private String password;
    private Set<GrantedAuthority> set;


    @Override
    public Collection<? extends GrantedAuthority> getAuthorities() {
        return this.set;
    }

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

    @Override
    public String getUsername() {
        return this.account;
    }

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

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

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

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

2.service:

public class MyUserDetailsService implements UserDetailsService {

    @Override
    public UserDetails loadUserByUsername(String token) throws UsernameNotFoundException {
        MyUserDetails userDetails = null;
        try {
            TUser tUser = JSON.parseObject(token, TUser.class);
            userDetails = new MyUserDetails();
            userDetails.setAccount(tUser.getAccount());
            userDetails.setPassword(tUser.getPassword());
            Integer role = tUser.getRole();
            Set<GrantedAuthority> set = new HashSet<>();
            set.add((new SimpleGrantedAuthority("ROLE_user")));
            if (role != null && role == 2) {
                set.add((new SimpleGrantedAuthority("ROLE_admin")));
            }
            userDetails.setSet(set);
        } catch (Exception e) {
            log.error("json error");
        }
        return userDetails;
    }
}

3.filter:

@Component
public class MyFilter extends OncePerRequestFilter {

    @Autowired
    private MyUserDetailsService myUserDetailsService;

    @Override
    protected void doFilterInternal(HttpServletRequest httpServletRequest
            , HttpServletResponse httpServletResponse, FilterChain filterChain) throws ServletException, IOException {
        String token = httpServletRequest.getHeader("token");
        if (token != null && SecurityContextHolder.getContext().getAuthentication() == null) {
            String info = JWTUtil.getInfo(token);
            boolean verify = JWTUtil.verify(token, info);
            if (verify) {
                UserDetails userDetails = myUserDetailsService.loadUserByUsername(info);
                if (userDetails != null) {
                    UsernamePasswordAuthenticationToken usernamePasswordAuthenticationToken =
                            new UsernamePasswordAuthenticationToken(userDetails.getUsername(), userDetails.getPassword(), userDetails.getAuthorities());
                    SecurityContextHolder.getContext().setAuthentication(usernamePasswordAuthenticationToken);
                }
            }
        }
        filterChain.doFilter(httpServletRequest, httpServletResponse);
    }
}

4.总配置:

@EnableWebSecurity
@Configuration
@EnableGlobalMethodSecurity(prePostEnabled = true)
public class SecurityConfig extends WebSecurityConfigurerAdapter {

    @Autowired
    private MyFilter myFilter;
    @Autowired
    private MyUserDetailsService myUserDetailsService;

    @Bean
    public BCryptPasswordEncoder bCryptPasswordEncoder() {
        return new BCryptPasswordEncoder();
    }

    @Override
    protected void configure(HttpSecurity http) throws Exception {
        http.csrf().disable()
                .sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS)
                .and()
                .httpBasic().disable().exceptionHandling()
                .authenticationEntryPoint((httpServletRequest, httpServletResponse, e) -> {
                    ret(httpServletResponse, true);
                })
                .accessDeniedHandler((httpServletRequest, httpServletResponse, e) -> {
                    ret(httpServletResponse, false);
                })
                .and()
                .authorizeRequests()
                .antMatchers("/login", "/swagger-ui.html*"
                        , "/swagger-resources/**", "/webjars/**", "/configuration/**"
                        , "/images/**", "/v2/api-docs/**")
                .permitAll()
                .and()
                .authorizeRequests()
                .anyRequest()
                .authenticated();
        http.addFilterBefore(myFilter, UsernamePasswordAuthenticationFilter.class);
    }

    @Override
    protected void configure(AuthenticationManagerBuilder auth) throws Exception {
        auth.userDetailsService(myUserDetailsService).passwordEncoder(bCryptPasswordEncoder());
    }

    public void ret(HttpServletResponse httpServletResponse, boolean flag) throws IOException {
        httpServletResponse.setContentType("application/json;charset=utf-8");
        if (flag) {
            httpServletResponse.getWriter().write(JSON.toJSONString(MyResponse.notLogin("登陆失败")));
        } else {
            httpServletResponse.getWriter().write(JSON.toJSONString(MyResponse.notAuth("没有权限")));
        }
    }
}

注解:

@PreAuthorize("hasRole('admin')")等

_________________12.15更新,关闭httpbase,使用lambda表达式响应登录结果

_________________12.25更新,springsecurity配置多provider登陆策略;

(1)配置自定义的token实体,还有两个provider;

public class Token extends AbstractAuthenticationToken {
    private String token;

    public String getToken() {
        return token;
    }

    public void setToken(String token) {
        this.token = token;
    }

    public Token(Collection<? extends GrantedAuthority> authorities) {
        super(authorities);
    }

    @Override
    public Object getCredentials() {
        return token;
    }

    @Override
    public Object getPrincipal() {
        return token;
    }

这里的token实体是来区分选择不同登陆处理器用的;

@Component
public class OneProvider implements AuthenticationProvider {

    private String user = "321";

    @Override
    public Authentication authenticate(Authentication authentication) throws AuthenticationException {
        String name = authentication.getName();
        TUser tUser = JSON.parseObject(name, TUser.class);
        if (tUser.getAccount().equalsIgnoreCase(user)) {
            return new UsernamePasswordAuthenticationToken(name, authentication.getCredentials(), new ArrayList<>());
        }
        return null;
    }

    @Override
    public boolean supports(Class<?> aClass) {
        System.out.println(aClass.getName());
        return aClass.equals(UsernamePasswordAuthenticationToken.class);
    }
}
@Component
public class TwoProvider implements AuthenticationProvider {

    private String user = "123";

    @Override
    public Authentication authenticate(Authentication authentication) throws AuthenticationException {
        String name = authentication.getName();
        TUser tUser = JSON.parseObject(name, TUser.class);
        if (tUser.getAccount().equalsIgnoreCase(user)) {
            return new UsernamePasswordAuthenticationToken(name, authentication.getCredentials(), new ArrayList<>());
        }
        return null;
    }

    @Override
    public boolean supports(Class<?> aClass) {
        System.out.println(aClass.getName());
        return aClass.equals(Token.class);
    }
}

这里是根据supports进行处理的,返回为true进行登陆处理;

@Component
public class OneFilter extends OncePerRequestFilter {
    @Autowired
    private MyUserDetailsService myUserDetailsService;

    @Override
    protected void doFilterInternal(HttpServletRequest httpServletRequest
            , HttpServletResponse httpServletResponse, FilterChain filterChain) throws ServletException, IOException {
        String token = httpServletRequest.getHeader("token");
        String type = httpServletRequest.getParameter("type");
        if (token != null && SecurityContextHolder.getContext().getAuthentication() == null) {
            String info = JWTUtil.getInfo(token);
            boolean verify = JWTUtil.verify(token, info);
            if (verify) {
                if (type == null) {
                    UsernamePasswordAuthenticationToken usernamePasswordAuthenticationToken =
                            new UsernamePasswordAuthenticationToken(info, null);
                    SecurityContextHolder.getContext().setAuthentication(usernamePasswordAuthenticationToken);
                } else {
                    Token token1 = new Token(new ArrayList<>());
                    token1.setToken(info);
                    SecurityContextHolder.getContext().setAuthentication(token1);
                }
            }
        }
        filterChain.doFilter(httpServletRequest, httpServletResponse);
    }
}

这里可以根据预先设计的登陆类别来判断封装成那种token类型;

@Override
    protected void configure(AuthenticationManagerBuilder auth) throws Exception {
        //  auth.userDetailsService(myUserDetailsService).passwordEncoder(bCryptPasswordEncoder());
        auth.authenticationProvider(oneProvider);
        auth.authenticationProvider(two);
    }

下面为配置多个登陆处理器;

12.29;

如果在realm中抛出自定义异常,想要被shiro自定义filter拦截的话,首先抛出的异常需要继承AuthenticationException,其次你的自定义过滤器需要继承 AuthorizationFilter;这样才能被拦截到.

还有如果使用@RequiresRoles()权限注解的话,在配置中配置的setUnauthorizedUrl无权限跳转地址是无法跳转的,因为他只能在过滤链中配置roles等时生效;所以可以使用控制层全局的异常处理;

1.11 修改shiro未登录及无权限返回状态;直接通过respose返回json,否则通过重定向会使前端url改变;并且删除配置中的跳转登陆页面及无权限页面,该处用token前后端分离方式登陆无效;

猜你喜欢

转载自blog.csdn.net/myth_g/article/details/84940000