博客
关于我
强烈建议你试试无所不能的chatGPT,快点击我
spring boot +spring security + jwt 实现认证模块
阅读量:6698 次
发布时间:2019-06-25

本文共 12731 字,大约阅读时间需要 42 分钟。

        我在使用spring进行开发时,通常是使用 aop+jwt 模式来对调用者身份进行确认。前几天接触到一个开源商城源码()里面使用spring security +jwt 来进行权限的验证。但是源码中只实现了简单的用户名密码验证,关于权限的略过了。虽然以前了解过spring security但是没有实际使用过,借着这个机会整合了一下spring security jwt。(可以拿起就用:smirk:)

        spring security 是基于spring的一个web安全框架。一般来说,web应用的安全性包括用户认证和用户授权两个部分。用户认证常见的就是用户名密码验证。用户授权则指的是查看用户是否有权限调用资源。

用户认证

对于用户认证,我们自定义的话通常需要自己实现 PasswordEncoder UserDetail两个类。PasswordEncoder主要实现了密码的加密,以及密码的比较(登陆时用户密码与数据库存储的密码)。我实现的PasswordEncoder代码如下

package com.lichaobao.springsecurityjwt.component;import org.slf4j.Logger;import org.slf4j.LoggerFactory;import org.springframework.security.crypto.password.PasswordEncoder;/** * @author lichaobao * @date 2018/12/22 * @QQ 1527563274 */public class MyPasswordEncoder implements PasswordEncoder {    private static final Logger LOGGER = LoggerFactory.getLogger(MyPasswordEncoder.class);    /**     * 自定义密码加密(出于示例,本代码没有对密码进行加密,直接返回原密码)     * @param charSequence 需要加密的密码     * @return 加密后的密码     */    @Override    public String encode(CharSequence charSequence) {        LOGGER.info("now encode password :{}",charSequence.toString());        return charSequence.toString();    }    /**     * 比较加密后的密码与数据库中的密码是否匹配     * @param charSequence 用户登陆传来的密码     * @param s 数据库中存储的密码     * @return true 匹配 false 不匹配     */    @Override    public boolean matches(CharSequence charSequence, String s) {        LOGGER.info("matchs charSequence :{} and password :{}",charSequence,s);        return encode(charSequence).equals(s);    }}复制代码

        对于UserDetails类来说主要起到了封装用户信息的作用,包括用户的基本信息以及拥有的权限信息的封装。默认UserDetails的生成类是 UserDetailsService。 这个接口中提供了loadUserByUsername(String username)方法UserDetailService源码如下

package org.springframework.security.core.userdetails;public interface UserDetailsService {    UserDetails loadUserByUsername(String var1) throws UsernameNotFoundException;}复制代码

在本例中 我们通过修改WebSecurityConfigAdapter中的UserdetailsServices实现代码如下

/**     * 在次代码中完成用户基本信息的查询比如用户名 密码 权限等封装后 返回     * 此方法的入口 为 userDetailsService.loadUserByUsername(String username)     * @return UserDetail     */    @Bean    @Override    protected UserDetailsService userDetailsService() {        return username ->{            if(users.containsKey(username)){                return new MyUserDetails(username,users.get(username),permissions.get(username));            }            throw new UsernameNotFoundException("用户名错误");        };    }复制代码

权限验证

        自定义权限验证我们通过自定义AccesDecisionVoter类来实现。关键代码如下

/** * @author lichaobao * @date 2018/12/22 * @QQ 1527563274 */public class RoleBasedVotor implements AccessDecisionVoter {    private static final Logger LOGGER = LoggerFactory.getLogger(RoleBasedVotor.class);    @Override    public boolean supports(ConfigAttribute configAttribute) {        return true;//根据自己的逻辑修改  不能直接return false否则验证不通过    }    /**     * 主要验证逻辑     * ROLE_ANONYMOUS 代表所有人可以访问 这是 spring security 自动生成的 可以自定义     * @param authentication 用户信息     * @param o  可以从这里拿到url     * @param collection 访问资源需要的权限在本例中由于我们将url作为验证依据所以为用到collection     * @return ACCESS_DENIED(-1)无权限 ACCESS_GRANTED(1)有权限     */    @Override    public int vote(Authentication authentication, Object o, Collection collection) {        FilterInvocation fi = (FilterInvocation) o;        String url = fi.getRequestUrl();        LOGGER.info("url :{}",url);        if(authentication == null){            return ACCESS_DENIED;        }        Collection
authorities = extractAuthorities(authentication); Iterator iterator = authorities.iterator(); while (iterator.hasNext()){ GrantedAuthority ga = (GrantedAuthority) iterator.next(); LOGGER.info(ga.getAuthority()); if(equalsurl(url,ga.getAuthority())||"ROLE_ANONYMOUS".equals(ga.getAuthority())){ return ACCESS_GRANTED; } } return ACCESS_DENIED; } @Override public boolean supports(Class aClass) { return true;//根据自己的逻辑修改 不能直接return false否则验证不通过 } /** * 获得用户权限信息 * @param authentication * @return */ Collection
extractAuthorities( Authentication authentication) { LOGGER.info("extractAuthorites:{}",authentication.getAuthorities()); return authentication.getAuthorities(); } /** * 比较权限 权限 /** 代表 以下所有能访问 /* 代表以下一级能访问 如 用户权限为 /test/** 则能访问 /test/a /test/b/c * 如用户权限为 /test/* 则能访问 /test/a 而 /test/b/c则不能访问 * @param url 访问的url * @param urlpermission 拥有的权限 * @return boolean */ static boolean equalsurl(String url,String urlpermission) { url = url.startsWith("/") ? url.substring(1):url; urlpermission = urlpermission.startsWith("/")?urlpermission.substring(1):urlpermission; if("**".equals(urlpermission)){ return true; }else if("*".equals(urlpermission)){ return url.split("/").length == 1; } else if(urlpermission.endsWith("/**")){ String afterUrl = urlpermission.substring(0,urlpermission.length()-3); return url.startsWith(afterUrl); }else if(urlpermission.endsWith("/*")){ String afterUrl = urlpermission.substring(0,urlpermission.length()-2); String[] urlPiece = url.split("/"); return url.startsWith(afterUrl)&&urlPiece.length == 2; } return url.equals(urlpermission); }}复制代码

验证登陆

        在登陆逻辑的代码中,我们需要通过登陆接口接收到的用户名、密码生成UsernamePasswordAuthenticationToken 为接下来的验证提供一个桥梁。注意密码要根据自定义实现的PasswordEncoder中的加密方法或着其他自己实现的加密方法进行加密要保证加密后和数据库中的密码相对应。然后 通过调用authenticationManager中的authenticate方法进行验证。具体代码如下

@Servicepublic class SignServiceImpl implements SignService {    private static final Logger LOGGER = LoggerFactory.getLogger(SignService.class);    @Autowired    AuthenticationManager authenticationManager;    @Autowired    UserDetailsService userDetailsService;    @Autowired    JwtUtils jwtUtils;    @Autowired    PasswordEncoder passwordEncoder;    /**     * 登陆 登陆出现错误抛出错误 用catch接受即可     * @param username 用户名     * @param password 密码     * @return String token     */    @Override    public String login(String username, String password) {        String token = null;        /**         * 封装 注意密码加密         */        UsernamePasswordAuthenticationToken authenticationToken =                new UsernamePasswordAuthenticationToken(username,passwordEncoder.encode(password));        try{            Authentication authentication = authenticationManager.authenticate(authenticationToken);            SecurityContextHolder.getContext().setAuthentication(authentication);            /**             * 加载数据库中的用户名密码 主要逻辑为UserdetailsServices中的代码             */            userDetailsService.loadUserByUsername(username);            token = jwtUtils.generateToken(username);        }catch (Exception e){            e.printStackTrace();            LOGGER.info("认证失败 :{}",e.getMessage());        }        return token;    }}复制代码

验证登陆凭证

        具体实现为继承OncePerRequestFilter方法实现自己的Filter ,通过解析token获得用户信息,然后比对用户权限。代码如下:

/** * @author lichaobao * @date 2018/12/22 * @QQ 1527563274 */public class JwtAuthenticationTokenFilter extends OncePerRequestFilter {    private static final Logger LOGGER = LoggerFactory.getLogger(JwtAuthenticationTokenFilter.class);    @Autowired    JwtUtils jwtUtils;    @Autowired    UserDetailsService userDetailsService;    @Override    protected void doFilterInternal(HttpServletRequest request,                                    HttpServletResponse response,                                    FilterChain filterChain) throws ServletException, IOException {        String token = request.getHeader("token");        if(token != null && SecurityContextHolder.getContext().getAuthentication() == null){            String username = jwtUtils.getUserNameFromToken(token);            UserDetails userDetails = userDetailsService.loadUserByUsername(username);            UsernamePasswordAuthenticationToken authenticationToken =                    new UsernamePasswordAuthenticationToken(userDetails,null,userDetails.getAuthorities());            LOGGER.info("UserDetails :{},permissions:{}",userDetails.getUsername(),userDetails.getAuthorities());            authenticationToken.setDetails(new WebAuthenticationDetailsSource().buildDetails(request));            LOGGER.info("authenticated user :{}",username);            LOGGER.info("already filter name:{}",super.getAlreadyFilteredAttributeName());            SecurityContextHolder.getContext().setAuthentication(authenticationToken);        }        filterChain.doFilter(request,response);    }}复制代码

配置security

        具体实现为继承WebSecurityConfigurerAdapter方法 根据自己的需要重写逻辑

/** * @author lichaobao * @date 2018/12/22 * @QQ 1527563274 */@Configuration@EnableWebSecuritypublic class MySecurityConfig extends WebSecurityConfigurerAdapter {    @Autowired    MyAccessDeineHandler myAccessDeineHandler;    @Autowired    MyAuthenticationEntryPoint myAuthenticationEntryPoint;    /**     * 模拟数据库用户     */    private static Map
users; /** * 模拟权限 */ private static Map
> permissions; static { users = new HashMap<>(); permissions = new HashMap<>(); users.put("a","a"); String[] aper = new String[]{
"/a/**","/test/all"}; permissions.put("a",Arrays.asList(aper)); users.put("b","b"); String[] bper = new String[]{
"/b/**","test/all"}; permissions.put("b",Arrays.asList(bper)); users.put("admin","password"); String[] adminPer = new String[]{
"/**"}; permissions.put("admin",Arrays.asList(adminPer)); } @Override protected void configure(HttpSecurity http) throws Exception { http.csrf() .disable()//禁用csrf 因为使用jwt不需要 .sessionManagement() .sessionCreationPolicy(SessionCreationPolicy.STATELESS)//禁用session .and() .authorizeRequests() .accessDecisionManager(accessDecisionManager())//加载自己的accessDecisionManager 用到了RoleBasedVotor .antMatchers(HttpMethod.GET, "/", "/*.html", "/favicon.ico", "/**/*.html", "/**/*.css", "/**/*.js", "/swagger-resources/**", "/v2/api-docs/**") .permitAll()//允许访问所有界面资源 .antMatchers("/login","/register") .permitAll()//允许访问登陆注册接口 然后 "/login"与"/register"的权限为"ROLE_ANONYMOUS" .antMatchers(HttpMethod.OPTIONS) .permitAll()//跨域请求 会有一个OPTIONS 请求 全部允许 .anyRequest()//其他任何都需要验证 .authenticated(); /** * 禁用缓存 */ http.headers().cacheControl(); /** * 配置自定义的Filter */ http.addFilterBefore(jwtAuthenticationTokenFilter(),UsernamePasswordAuthenticationFilter.class); /** * 配置自定义的无权限以及用户名密码错误返回结果 */ http.exceptionHandling() .accessDeniedHandler(myAccessDeineHandler) .authenticationEntryPoint(myAuthenticationEntryPoint); } /** * 配置 userDetailsService 以及passwordEncoder; * @param auth * @throws Exception */ @Override protected void configure(AuthenticationManagerBuilder auth) throws Exception { auth.userDetailsService(userDetailsService()) .passwordEncoder(passwordEncoder()); } /** * 在次代码中完成用户基本信息的查询比如用户名 密码 权限等封装后 返回 * 此方法的入口 为 userDetailsService.loadUserByUsername(String username) * @return UserDetail */ @Bean @Override protected UserDetailsService userDetailsService() { return username ->{ if(users.containsKey(username)){ return new MyUserDetails(username,users.get(username),permissions.get(username)); } throw new UsernameNotFoundException("用户名错误"); }; } @Bean public PasswordEncoder passwordEncoder(){ return new MyPasswordEncoder(); } @Bean public JwtAuthenticationTokenFilter jwtAuthenticationTokenFilter(){ return new JwtAuthenticationTokenFilter(); } @Bean @Override public AuthenticationManager authenticationManagerBean() throws Exception { return super.authenticationManagerBean(); } /** * 具体使用RoleBasedVotor方法 * @return */ @Bean public AccessDecisionManager accessDecisionManager(){ List
> decisionVoters = Arrays.asList( new WebExpressionVoter(), new RoleBasedVotor(), new AuthenticatedVoter()); return new UnanimousBased(decisionVoters); }}复制代码

转载于:https://juejin.im/post/5cf47e9ae51d45590a445aef

你可能感兴趣的文章
UPS开始尝试“货车+无人机”的投递方式,不必再担心快递员离职了
查看>>
前端入门教程(七)CSS属性设置
查看>>
我所知道的Promise
查看>>
20180601]函数与标量子查询2.txt
查看>>
交换2个数值的方法
查看>>
“docker-app”实用工具分享,大大提高 Compose 文件复用率
查看>>
位置参数及操作符号
查看>>
伪共享和缓存行填充,Java并发编程还能这么优化!
查看>>
数据库备份DBS商业化发布
查看>>
聊聊3种最常见的响应式设计问题
查看>>
.NET面试题解析(02)-拆箱与装箱
查看>>
高性能、高可靠分布式文件系统 go-fastdfs v1.2.0 发布
查看>>
VR全景看年评!PConline年度评测盛典等你来体验
查看>>
为旗下硬件产品服务,LG推出基于SLAM技术的3D摄像头
查看>>
必应(Bing)每日图片获取API
查看>>
Spring MVC-表单(Form)标签-下拉框(Dropdown)示例(转载实践)
查看>>
Atom飞行手册翻译: 2.7 ~ 2.10
查看>>
Invoice Application Front-end Using ElectronJS
查看>>
redis的配置文件
查看>>
用 Python 语言来写游戏
查看>>