说明 : Spring Security 做系统权限的,引入轻松,涵盖各种粒度的权限控制,可以自定义各种处理器,拦截器等.
5.7版本之前,整合
Spring Security
引入 security 启动器,默认
拦截所有资源
,启动项目会生成一个默认密码,账号user
<!-- 引入Spring Security -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-security</artifactId>
</dependency>
不前后分离,单线开发时 👇
- 实现
UserDetailsService
, 配置SecurityUser
或者返回security 的User
,自定义用户登录逻辑- 继承
WebSecurityConfigurerAdapter
,定义加密方式
,添加自定义拦截器
、各种Handler
、登录异常、权限不足等…
@Data
@Accessors(chain = true)
public class SecurityUser implements UserDetails {
private static final long serialVersionUID = -2211380247224432737L;
private static final String DEFAULT_AUTH="ADMIN";
private Long userId;
private String userName;
private String account;
private String password;
/**
* 用户角色,权限集合
*/
private List<String> rolesAuthorities;
/**
* 是否可用
*/
private boolean enabled = true;
/**
* 是否冻结
*/
private boolean locked = true;
/**
* 设置登录用户信息
* @param user user 登录用户信息
* @return SecurityUser
*/
public SecurityUser setUser(User user,List<String> rolesAuthorities){
this.userId=user.getId();
this.userName=user.getUserName();
this.account=user.getPhone();
this.password=user.getPassWord();
this.rolesAuthorities=rolesAuthorities;
return this;
}
/**
* 登录用户角色
* @return List<GrantedAuthority>
*/
@Override
public Collection<? extends GrantedAuthority> getAuthorities() {
List<GrantedAuthority> authorities = new LinkedList<>();
if (rolesAuthorities!=null && rolesAuthorities.size()>0){
for (String role : rolesAuthorities) {
authorities.add(new SimpleGrantedAuthority(role));
}
}else {
authorities=AuthorityUtils.commaSeparatedStringToAuthorityList(DEFAULT_AUTH);
}
return authorities;
}
@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 locked;
}
@Override
public boolean isCredentialsNonExpired() {
return true;
}
@Override
public boolean isEnabled() {
return enabled;
}
}
@Service
public class UserDetailServiceImpl implements UserDetailsService {
@Resource
UserMapper userMapper;
@Override
public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
//username :前面提交登录账号
User user = userMapper.selectOne(new User().setPhone(username));
if (user==null){
throw new UsernameNotFoundException("账号不存在");
}
//模拟数据查询用户角色集合,权限集合,角色用ROLE_ 开头
List<String> rolesAuthorities = Arrays.asList("vip1", "vip2","ROLE_test","ROLE_vip");
return new SecurityUser().setUser(user,rolesAuthorities);
}
}
@Configuration
public class SecurityConfig extends WebSecurityConfigurerAdapter {
@Resource
private MyAccessDeniedHandler myAccessDeniedHandler;
@Override
protected void configure(HttpSecurity http) throws Exception {
//登录配置 ,如果不前后分离,重新这方法就要配置登录跳转地址了,不然就404
http.formLogin()
//自定义:登录接口
.loginProcessingUrl("/login")
//定义登录请求参数
.usernameParameter("username")
.passwordParameter("password")
//自定义:登录页面地址
.loginPage("/toLogin")
//登录成功跳转地址
.loginProcessingUrl("/main")
//登录失败跳转地址
.failureForwardUrl("/toError");
//注销配置
http.logout()
//注销地址
.logoutUrl("/logout");
//退出登录跳转页面
//.logoutSuccessUrl("/login.html");
//拦截配置
//http.addFilter();
//授权配置
http.authorizeRequests()
//访问 /user 需要权限标识 vip1
.antMatchers("/user").hasAuthority("vip1")
//访问 /admin 需要角色 admin
.antMatchers("/admin").hasAnyRole("admin")
//添加用户post请求 需要角色 admin
.regexMatchers(HttpMethod.POST,"/addUser").hasAnyRole("admin")
//放行静态资源
.antMatchers("/js/**","/css/**","/images/**").permitAll()
//放行 option 请求
.antMatchers(HttpMethod.OPTIONS, "/**").permitAll()
//放行 /test 所有请求
.antMatchers("/test/**").permitAll()
//登录相关请求
.antMatchers("/login/**").permitAll()
//之外所有请求需要认证
.anyRequest().authenticated();
//自定义异常处理
http.exceptionHandling()
//403异常
.accessDeniedHandler(myAccessDeniedHandler);
//关闭scrf防护
// http.csrf().disable();
}
/**
* 配置加密方式
* @return PasswordEncoder
*/
@Bean
public PasswordEncoder passwordEncoder(){
return new BCryptPasswordEncoder();
}
}
整合
Jwt
,前后分离开发时
- 配置,登录拦截
LoginFilter extends UsernamePasswordAuthenticationFilter
- 配置,验证拦截
AuthenticationFilter extends BasicAuthenticationFilter
- 自定义 异常处理,403
RestAuthenticationAccessDeniedHandler implements AccessDeniedHandler
,失效令牌AuthEntryPoint implements AuthenticationEntryPoint
- 相当于 token校检、失效丢给 Jwt 处理,接口鉴权,忽略认证等用Security处理
整合代码:
//自定义 403异常
public class RestAuthenticationAccessDeniedHandler implements AccessDeniedHandler {
public RestAuthenticationAccessDeniedHandler() {}
@Override
public void handle(HttpServletRequest request, HttpServletResponse response, AccessDeniedException e) throws IOException, ServletException {
ResponseUtil.write(response, LoginResult.error("没有权限"));
}
}
//无效令牌异常
public class AuthEntryPoint implements AuthenticationEntryPoint {
@Override
public void commence(HttpServletRequest request, HttpServletResponse response, AuthenticationException e){
ResponseUtil.write(response, LoginResult.error("令牌无效"));
}
}
//配置类
@Data
@Component
@ConfigurationProperties(prefix = "security")
public class SecurityProperties {
/**
* 忽略拦截路径
*/
private List<String> httpIgnore;
/**
* 请求头名称
*/
private String tokenHeader;
/**
* 请求头前缀
*/
private String tokenPrefix;
/**
* 令牌加密密匙
*/
private String secret;
/**
* 失效时间 /分钟 - 默认1小时
*/
private Long expiration;
}
//token生成工具
@Component
public class JwtTokenUtil {
@Resource
private SecurityProperties securityProperties;
public JwtTokenUtil() {}
/**
* token生成
* @param user
* @return
*/
public String createToken(SecurityUser user) {
String secret = this.securityProperties.getSecret();
if (secret==null){
secret="secret";
}
Long expiration = this.securityProperties.getExpiration();
if (expiration==null){
expiration=60L;
}
long time = expiration * 60L;
HashMap<String, Object> map = new HashMap<>(1);
map.put("user", user);
return Jwts.builder()
.setClaims(map)
.setSubject(user.getUsername())
.setExpiration(new Date(System.currentTimeMillis() + time * 1000L))
.signWith(SignatureAlgorithm.HS512, secret).compact();
}
public String getUserName(String token) {
return this.generateToken(token).getSubject();
}
private Claims generateToken(String token) {
String secret = this.securityProperties.getSecret();
if (secret==null){
secret="secret";
}
return Jwts.parser().setSigningKey(secret).parseClaimsJws(token).getBody();
}
public SecurityUser getSecurityUser(String token){
Claims claims = this.generateToken(token);
Map map = claims.get("user", Map.class);
return JSON.parseObject(JSON.toJSONString(map), SecurityUser.class);
}
}
//登录拦截
public class JwtLoginFilter extends UsernamePasswordAuthenticationFilter {
private static final String REQUEST_METHOD="POST";
private static final String DATA_FORMAT="json";
@Resource
private JwtTokenUtil jwtTokenUtil;
@Resource
private SecurityProperties securityProperties;
public JwtLoginFilter(AuthenticationManager authenticationManager) {
super.setAuthenticationManager(authenticationManager);
}
@Override
public Authentication attemptAuthentication(HttpServletRequest request, HttpServletResponse response) throws AuthenticationException {
if (!REQUEST_METHOD.equals(request.getMethod())) {
throw new AuthenticationServiceException("Authentication method not supported: " + request.getMethod());
} else {
String contentType = request.getHeader("Content-Type");
SecurityUser user = null;
//json提交
if (contentType.contains(DATA_FORMAT)) {
user = this.getSecurityUser(request);
if (user == null || user.getUsername() == null || user.getPassword() == null) {
throw new AuthenticationServiceException("Authentication failure: username or password can't be null.");
}
logger.info("账号登录:"+user.getUsername());
} else {
//表单提交
String username = this.obtainUsername(request);
String password = this.obtainPassword(request);
if (username == null || password == null) {
throw new AuthenticationServiceException("Authentication failure: username or password can't be null.");
}
logger.info("账号登录:"+username);
user = (new SecurityUser()).setUsername(username.trim()).setPassword(password);
}
request.setAttribute("account",user.getUsername());
return this.getAuthenticationManager().authenticate(new UsernamePasswordAuthenticationToken(user.getUsername(), user.getPassword(), new ArrayList()));
}
}
@Override
protected void successfulAuthentication(HttpServletRequest request, HttpServletResponse response, FilterChain chain, Authentication authResult) throws IOException, ServletException {
SecurityUser user = (SecurityUser)authResult.getPrincipal();
user.setPassword("");
String token = this.jwtTokenUtil.createToken(user);
String tokenPrefix = this.securityProperties.getTokenPrefix();
if (!StringUtils.hasLength(tokenPrefix)){
tokenPrefix="Bearer";
}
String tokenHeader = this.securityProperties.getTokenHeader();
if (!StringUtils.hasLength(tokenHeader)){
tokenHeader="Authorization";
}
response.addHeader(tokenHeader, tokenPrefix + token);
logger.info("登录成功,用户: "+user.getUsername());
ResponseUtil.write(response, LoginResult.login(tokenPrefix + token));
}
//对应 ->JwtUserDetailsServiceImpl loadUserByUsername()
@Override
protected void unsuccessfulAuthentication(HttpServletRequest request, HttpServletResponse response, AuthenticationException failed) throws IOException, ServletException {
String msg;
String account = (String)request.getAttribute("account");
if (failed instanceof UsernameNotFoundException) {
msg="账号不存在";
}else if ( failed instanceof BadCredentialsException){
msg="密码输入错误";
}else if (failed instanceof DisabledException) {
msg="用户账号已被禁用";
} else if (failed instanceof LockedException) {
msg="抱歉您的账户已被锁定";
} else if (failed instanceof AccountExpiredException) {
msg="账户过期";
} else {
msg="登录失败";
}
logger.info("登录失败,账号: "+account);
ResponseUtil.write(response, LoginResult.error(msg));
}
private SecurityUser getSecurityUser(HttpServletRequest request) {
StringBuilder sb = new StringBuilder();
try {
InputStream is = request.getInputStream();
InputStreamReader isr = new InputStreamReader(is);
BufferedReader br = new BufferedReader(isr);
String s = "";
while((s = br.readLine()) != null) {
sb.append(s);
}
return JSON.parseObject(sb.toString(), SecurityUser.class);
} catch (IOException var7) {
return null;
}
}
}
//鉴权拦截
@Slf4j
public class JwtAuthenticationFilter extends BasicAuthenticationFilter {
@Resource
private JwtTokenUtil jwtTokenUtil;
@Resource
private SecurityProperties securityProperties;
public static final List<String> HTTP_IGNORE = new LinkedList<>(Arrays.asList("/doc.html", "/swagger-resources", "/v3/api-docs/**", "/webjars/**","/logout"));
public JwtAuthenticationFilter(AuthenticationManager authenticationManager) {
super(authenticationManager);
}
@Override
protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain chain)
throws IOException, ServletException {
//忽略拦截路径放行
if (this.ignore(request)) {
chain.doFilter(request, response);
} else {
String tokenHeader = this.securityProperties.getTokenHeader();
if (!StringUtils.hasLength(tokenHeader)){
tokenHeader="Authorization";
}
//请求头获取
String header = request.getHeader(tokenHeader);
//前缀匹配
String tokenPrefix = this.securityProperties.getTokenPrefix();
if (!StringUtils.hasLength(tokenPrefix)){
tokenPrefix="Bearer";
}
if (header != null && header.startsWith(tokenPrefix)) {
UsernamePasswordAuthenticationToken authenticationToken = null;
try {
authenticationToken = this.getAuthentication(header);
} catch (Exception e) {
ResponseUtil.write(response,LoginResult.error("非法token"));
return;
}
SecurityContextHolder.getContext().setAuthentication(authenticationToken);
chain.doFilter(request, response);
} else {
ResponseUtil.write(response, LoginResult.error("无效令牌"));
}
}
}
/**
* 这里从token中获取用户信息并新建一个token
*/
private UsernamePasswordAuthenticationToken getAuthentication(String header) {
String tokenPrefix = this.securityProperties.getTokenPrefix();
if (!StringUtils.hasLength(tokenPrefix)){
tokenPrefix="Bearer";
}
String token = header.replace(tokenPrefix, "");
String principal = this.jwtTokenUtil.getUserName(token);
if (principal != null) {
SecurityUser user = this.jwtTokenUtil.getSecurityUser(token);
return new UsernamePasswordAuthenticationToken(user, null, user.getAuthorities());
} else {
return null;
}
}
/**
* 校检是否忽略路径,默认放行路径 /logOut
* @param request request
* @return boolean
*/
private boolean ignore(HttpServletRequest request) {
for (String ignore : HTTP_IGNORE) {
if (new AntPathRequestMatcher(ignore).matches(request)) {
return true;
}
}
return false;
}
/**
* 初始化忽略认证
*/
@PostConstruct
public void initIgnore(){
List<String> curIgnore = securityProperties.getHttpIgnore();
HTTP_IGNORE.addAll(curIgnore);
}
}
//自定义密码解析
public class MD5PasswordEncoder implements PasswordEncoder {
//TODO 加盐
private static String salt="xiaoshu@730!@#$/";
@Override
public String encode(CharSequence charSequence) {
return DigestUtils.md5Hex(charSequence +salt);
}
@Override
public boolean matches(CharSequence charSequence, String s) {
String s1 = DigestUtils.md5Hex(charSequence +salt);
return s1.equals(s);
}
}
//配置类
@Configuration
public class SecurityConfig extends WebSecurityConfigurerAdapter {
@Resource
private UserDetailsService loginUserDetailsService;
@Resource
private SecurityProperties properties;
@Override
protected void configure(HttpSecurity http) throws Exception {
//拦截配置
//登录拦截
http.addFilter(this.jwtLoginFilter())
//认证拦截
.addFilter(this.jwtAuthenticationFilter())
.exceptionHandling()
//自定义403
.accessDeniedHandler(new RestAuthenticationAccessDeniedHandler())
//自定义令牌失效
.authenticationEntryPoint(new AuthEntryPoint());
//默认放行配置
http.authorizeRequests().antMatchers(HttpMethod.GET,"/js/**","/css/**","/images/**").permitAll()
.antMatchers("/doc.html", "/swagger-resources", "/v3/api-docs/**", "/webjars/**","/logout").permitAll()
.antMatchers(HttpMethod.OPTIONS,"/**").permitAll()
//项目放行配置
.antMatchers(properties.getHttpIgnore().toArray(new String[]{})).permitAll().anyRequest().authenticated();
//关闭csrf
http.csrf().disable();
}
@Override
protected void configure(AuthenticationManagerBuilder auth) {
auth.authenticationProvider(this.authenticationProvider());
}
@Bean
@Override
public AuthenticationManager authenticationManagerBean() throws Exception {
return super.authenticationManagerBean();
}
@Bean
public JwtLoginFilter jwtLoginFilter() throws Exception {
return new JwtLoginFilter(authenticationManager());
}
@Bean
public JwtAuthenticationFilter jwtAuthenticationFilter() throws Exception {
return new JwtAuthenticationFilter(authenticationManager());
}
@Bean
public DaoAuthenticationProvider authenticationProvider() {
DaoAuthenticationProvider provider = new DaoAuthenticationProvider();
provider.setHideUserNotFoundExceptions(false);
provider.setUserDetailsService(loginUserDetailsService);
provider.setPasswordEncoder(new MD5PasswordEncoder());
return provider;
}
@Bean
public PasswordEncoder passwordEncoder(){
return new MD5PasswordEncoder();
}
}
/**
* 自定义登录逻辑
*/
@Service
public class UserDetailServiceImpl implements UserDetailsService {
@Autowired
private PasswordEncoder passwordEncoder;
@Override
public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
if (!username.equals("admin")){
throw new UsernameNotFoundException("用户不存在");
}
String password = passwordEncoder.encode("123");
return new SecurityUser().setUsername(username).setPassword(password).setRoles(Arrays.asList("ROLE_admin,","ROLE_vip","menu_del","auth"));
}
}
5.7 版本整合,待整理…