pom
<!--SpringSecurity及JWT依赖配置-->
<dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-security</ artifactId></dependency>
<!--Hutool Java工具包-->
<dependency><groupId>cn.hutool</groupId><artifactId>hutool-all</artifactId><version>4.5.7</version>
</dependency>
<!--了MT(]son web Token)登录支持-->
<dependency><groupId>io.jsonwebtoken</groupId><artifactId>jjwt</artifactId><version>0.9.o</version>
</dependency>
前台部分
公共配置类
@Configuration
public class SecurityConfig extends WebSecurityConfigurerAdapter {/*** 权限配置 白名单,jwt认证等等* @param http* @throws Exception*/@Overrideprotected void configure(HttpSecurity http) throws Exception {//放行白名单ExpressionUrlAuthorizationConfigurer<HttpSecurity>.ExpressionInterceptUrlRegistry registry = http.authorizeRequests();//循环白名单进行放行for (String url : ignoreUrlsConfig().getUrls()) {registry.antMatchers(url).permitAll();}//跨域请求//允许可以请求OPTIONS CORSregistry.antMatchers(HttpMethod.OPTIONS).permitAll();//其他请求都需要身份认证registry.anyRequest().authenticated()// 关闭csrf跨站请求伪造 :因为现在使用jwt来实现认证.and().csrf().disable()// 禁止session 性能更好.sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS)// 自定义权限拒绝处理类.and().exceptionHandling()// 没有权限访问时的处理类.accessDeniedHandler(restfulAccessDeniedHandler())//没有登录时的处理类.authenticationEntryPoint(restfulAuthenticationEntryPoint()).and()//加入jwt认证过滤器.addFilterBefore(jwtAuthenticationFilter(), UsernamePasswordAuthenticationFilter.class);}@Beanpublic JwtAuthenticationFilter jwtAuthenticationFilter(){return new JwtAuthenticationFilter();}@Beanpublic IgnoreUrlsConfig ignoreUrlsConfig(){return new IgnoreUrlsConfig();}@Beanpublic RestfulAccessDeniedHandler restfulAccessDeniedHandler(){return new RestfulAccessDeniedHandler();}@Beanpublic RestfulAuthenticationEntryPoint restfulAuthenticationEntryPoint(){return new RestfulAuthenticationEntryPoint();}@Beanpublic PasswordEncoder passwordEncoder(){return new BCryptPasswordEncoder();}
}
白名单绑定类
secure:ignored:urls: #安全路径白名单- /swagger-ui.html- /swagger-resources/**- /swagger/**- /**/v2/api-docs- /**/*.js- /**/*.css- /**/*.png- /**/*.ico- /webjars/springfox-swagger-ui/**- /actuator/**- /druid/**- /user/**- /home/**- /product/**- /order/paySuccess
@ConfigurationProperties(prefix = "secure.ignored")
public class IgnoreUrlsConfig {List<String> urls;public List<String> getUrls() {return urls;}public void setUrls(List<String> urls) {this.urls = urls;}
}
没有权限访问时的响应处理类
/*** @author* 没有权限访问时的响应处理类*/public class RestfulAccessDeniedHandler implements AccessDeniedHandler {@Overridepublic void handle(HttpServletRequest request, HttpServletResponse response, AccessDeniedException e) throws IOException, ServletException {response.setCharacterEncoding("UTF-8");response.setContentType("application/json");//响应403没有相关权限JSON json = JSONUtil.parse(CommonResult.failed(ResultCode.FORBIDDEN));// FORBIDDEN(403, "没有相关权限"),response.getWriter().print(json);response.getWriter().flush();}
}
未登录处理类
/*** @author* 未登录时处理类*/
public class RestfulAuthenticationEntryPoint implements AuthenticationEntryPoint {@Overridepublic void commence(HttpServletRequest request, HttpServletResponse response, AuthenticationException e) throws IOException, ServletException {response.setCharacterEncoding("UTF-8");response.setContentType("application/json");// 响应401 暂未登录或session已经过期JSON json = JSONUtil.parse(CommonResult.failed(ResultCode.UNAUTHORIZED));//UNAUTHORIZED(401, "暂未登录或session已经过期"),response.getWriter().print(json);}
}
JWT类
/*** @author* JWT 过滤器*/
public class JwtAuthenticationFilter extends OncePerRequestFilter {@AutowiredHttpServletRequest request;@Value("${jwt.tokenHeader}")private String tokenHeader;@Value("${jwt.tokenHead}")private String tokenHead;@Autowiredprivate JwtTokenUtil jwtTokenUtil;@Autowiredprivate UserDetailsService userDetailsService;@Overrideprotected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) throws ServletException, IOException {//jwt令牌 获取jwt令牌String jwt = request.getHeader(tokenHeader);if (!StrUtil.isBlank(jwt)&& jwt.startsWith(tokenHead)) {//字符串截取 截取Head 后面的字符串jwt =jwt.substring(tokenHead.length());//解密String userName = jwtTokenUtil.getUserNameFromToken(jwt);if (!StrUtil.isBlank(userName)) {//从服务器中查询用户 判断是否存在该用户UserDetails userDetails = userDetailsService.loadUserByUsername(userName);if (userDetails!=null) {//生成springsecurity的通过认证标识UsernamePasswordAuthenticationToken token = new UsernamePasswordAuthenticationToken(userDetails,null,userDetails.getAuthorities());SecurityContextHolder.getContext().setAuthentication(token);}}
// filterChain.doFilter(request,response);//放行}filterChain.doFilter(request,response);//放行// response.setCharacterEncoding("UTF-8");
// response.setContentType("application/json");
// //JWT为空
// JSON json = JSONUtil.parse("用户名不存在");
// response.getWriter().print(json);
// response.getWriter().flush();}
}
jwt:secret: tuling-mall-portal #可以用MD5加密 服务端私钥expiration: 86400 #24*60*60 一天tokenHead: Bearer #JWT规范 #告诉客户端JWT令牌开头需要加的一个字符串tokenHeader: Authorization #告诉客户端在请求头里传什么参数名
配置UserDetailsSerrvice
启动注解(具体子类模块中 代码)
@EnableWebSecurity
登录方法
@Autowiredprivate PasswordEncoder passwordEncoder;@Overridepublic UmsMember login(String username, String password) {UmsMember umsMember = null;try {UserDetails userDetails = loadUserByUsername(username);umsMember= ((MemberDetails) userDetails).getUmsMember();if (!passwordEncoder.matches(password,umsMember.getPassword())) {Asserts.fail("密码不正确");}//生成springsecurity的通过认证标识UsernamePasswordAuthenticationToken authenticationToken = new UsernamePasswordAuthenticationToken(userDetails,null,userDetails.getAuthorities());SecurityContextHolder.getContext().setAuthentication(authenticationToken);if (!userDetails.isEnabled()) {Asserts.fail("账号已被禁用");}insertLoginLog(username);//添加登录记录} catch (Exception e) {Asserts.fail("登录异常:" );e.printStackTrace();}return umsMember;}
/*** JwtToken生成的工具类* JWT token的格式:header.payload.signature* header的格式(算法、token的类型):* {"alg": "HS512","typ": "JWT"}* payload的格式(用户名、创建时间、生成时间):* {"sub":"wang","created":1489079981393,"exp":1489684781}* signature的生成算法:* HMACSHA512(base64UrlEncode(header) + "." +base64UrlEncode(payload),secret)* Created on 2018/4/26.*/
public class JwtTokenUtil {public static ThreadLocal<String> currentUsername=new ThreadLocal<>();private static final Logger LOGGER = LoggerFactory.getLogger(JwtTokenUtil.class);private static final String CLAIM_KEY_USERNAME = "user_name";private static final String CLAIM_KEY_CREATED = "created";@Value("${jwt.secret}")private String secret;@Value("${jwt.expiration}")private Long expiration;@Value("${jwt.tokenHead}")private String tokenHead;/*** 根据负责生成JWT的token*/private String generateToken(Map<String, Object> claims) {return Jwts.builder().setClaims(claims).setExpiration(generateExpirationDate()).signWith(SignatureAlgorithm.HS512, secret).compact();}/*** 从token中获取JWT中的负载*/private Claims getClaimsFromToken(String token) {Claims claims = null;try {claims = Jwts.parser().setSigningKey(secret).parseClaimsJws(token).getBody();} catch (Exception e) {LOGGER.info("JWT格式验证失败:{}", token);}return claims;}/*** 生成token的过期时间*/private Date generateExpirationDate() {return new Date(System.currentTimeMillis() + expiration * 1000);}/*** 解密:从token中获取登录用户名(项目使用)*/public String getUserNameFromToken(String token) {String username;try {Claims claims = getClaimsFromToken(token);username = claims.get(CLAIM_KEY_USERNAME,String.class);} catch (Exception e) {username = null;}return username;}/*** 加密: 根据用户名生成token(项目使用)*/public String generateUserNameStr(String username) {Map<String, Object> claims = new HashMap<>();claims.put(CLAIM_KEY_USERNAME,username);claims.put(CLAIM_KEY_CREATED, new Date());return generateToken(claims);}/*** 判断token是否已经失效*/private boolean isTokenExpired(String token) {Date expiredDate = getExpiredDateFromToken(token);return expiredDate.before(new Date());}/*** 从token中获取过期时间*/private Date getExpiredDateFromToken(String token) {Claims claims = getClaimsFromToken(token);return claims.getExpiration();}/*** 当原来的token没过期时是可以刷新的** @param oldToken 带tokenHead的token*/public String refreshHeadToken(String oldToken) {if(StrUtil.isEmpty(oldToken)){return null;}String token = oldToken.substring(tokenHead.length());if(StrUtil.isEmpty(token)){return null;}//token校验不通过Claims claims = getClaimsFromToken(token);if(claims==null){return null;}//如果token已经过期,不支持刷新if(isTokenExpired(token)){return null;}//如果token在30分钟之内刚刷新过,返回原tokenif(tokenRefreshJustBefore(token,30*60)){return token;}else{claims.put(CLAIM_KEY_CREATED, new Date());return generateToken(claims);}}/*** 判断token在指定时间内是否刚刚刷新过* @param token 原token* @param time 指定时间(秒)*/private boolean tokenRefreshJustBefore(String token, int time) {Claims claims = getClaimsFromToken(token);Date created = claims.get(CLAIM_KEY_CREATED, Date.class);Date refreshDate = new Date();//刷新时间在创建时间的指定时间内if(refreshDate.after(created)&&refreshDate.before(DateUtil.offsetSecond(created,time))){return true;}return false;}
}
后台部分
核心处理role,user,resource之间的关系
配置类
@Configuration
public class SecurityConfig extends WebSecurityConfigurerAdapter {/*** 前台服务没有动态权限功能*/@Autowired(required = false)private SecuriResourceRoleSource securiResourceRoleSource;@Overrideprotected void configure(HttpSecurity http) throws Exception {//其他代码if (securiResourceRoleSource != null) {Map<String, List<String>> map = securiResourceRoleSource.getResourceRole();//循环注册if (!map.isEmpty()) {Set<Map.Entry<String, List<String>>> entries = map.entrySet();
// for (Map.Entry<String, List<String>> entry : entries) {
// entry.getKey()
// }Iterator<Map.Entry<String, List<String>>> iterator = entries.iterator();while (iterator.hasNext()) {Map.Entry<String, List<String>> next = iterator.next();//list转换数据,object[]转换为String[]List<String> list = next.getValue();String[] toArray = list.toArray(new String[list.size()]);registry.antMatchers(next.getKey()).hasAnyAuthority(toArray);}}}//其他代码}
SecuriResourceRoleSource 类
/*** @author* 核心是获取 用户user 角色role 资源resource*/
public interface SecuriResourceRoleSource {/*** 获取所有资源对应的角色* @return*///key 资源 /product/**// value 角色Map<String, List<String>> getResourceRole();
}
@Autowiredprivate UmsResourceService resourceService;/*** 为Springsecurity配置的资源信息* @return*/@Beanpublic SecuriResourceRoleSource securiResourceRoleSource(){return new SecuriResourceRoleSource() {@Overridepublic Map<String, List<String>> getResourceRole() {//业务逻辑类 查询 对应的资源信息 用户 角色 资源三者关系List<ResourceRoleDto> list=resourceService.getResourceRole();Map<String, List<String>> map = new LinkedHashMap<>();for (ResourceRoleDto resourceRoleDto : list) {List<String> roleNameList = list.stream().map(r -> r.getName()).collect(Collectors.toList());map.put(resourceRoleDto.getUrl(),roleNameList);}return map;}};}
key为url
value为角色名
UserDetails中的getAuthorities()
List<UmsRole> roleList;/*** 返回当前用户的角色信息* @return*/@Overridepublic Collection<? extends GrantedAuthority> getAuthorities() {List<GrantedAuthority> authorities = roleList.stream().map(role -> {return new SimpleGrantedAuthority(role.getName());}).collect(Collectors.toList());return authorities;}
User 类中
@Overridepublic UserDetails loadUserByUsername(String username) {UmsMember umsMember = getAdminByUsername(username);//根据用户id返回用户的角色List<UmsRole> roleList = roleMapper.getRoleList(umsMember.getId());if (umsMember!=null) {return new MemberDetails(umsMember,roleList);}throw new ApiException("用户名或密码错误");}
动态发布
总的
在这里插入代码片