一、SpringSecurity 在一般Web工程下的使用(XML配置)
需要的依赖
<dependency>
<groupId>org.springframework.security</groupId>
<artifactId>spring-security-config</artifactId>
<version>4.2.10.RELEASE</version>
</dependency>
<dependency>
<groupId>org.springframework.security</groupId>
<artifactId>spring-security-taglibs</artifactId>
<version>4.2.10.RELEASE</version>
</dependency>
XML 配置文件
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:security="http://www.springframework.org/schema/security"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans-3.0.xsd
http://www.springframework.org/schema/security
http://www.springframework.org/schema/security/spring-security.xsd">
<!-- 放行静态资源-->
<security:http pattern="/css/**" security="none"/>
<security:http pattern="/image/**" security="none"/>
<security:http pattern="/js/**" security="none"/>
<!-- 是否自动加载 security 配置文件 -->
<!-- 是否使用 el 表达式-->
<security:http auto-config="true" use-expressions="true">
<!-- 登录界面不许要权限-->
<security:intercept-url pattern="/login2.jsp" access="permitAll()"/>
<security:intercept-url pattern="/failure.jsp" access="permitAll()"/>
<security:intercept-url pattern="/**" access="hasAnyRole('ROLE_USER')" />
<!-- 配置认证信息-->
<security:form-login login-page="/login2.jsp"
login-processing-url="/login"
default-target-url="/index.jsp"
authentication-failure-url="/failure.jsp"/>
<security:logout logout-url="/logout"
logout-success-url="/login2.jsp"/>
<!-- 是否禁止使用自带的 csrf -->
<security:csrf disabled="true"/>
<!-- 开启 remember me 功能 的过滤器 配置有效期 配置数据库源(用于转换token)配置前端传参 name -->
<security:remember-me token-validity-seconds="6000"
data-source-ref="dataSource"
remember-me-parameter="remember-me"/>
<security:access-denied-handler error-page="/403.jsp"/>
</security:http>
<!-- 手动添加账号密码提供者-->
<!-- <security:authentication-manager>-->
<!-- <security:authentication-provider>-->
<!-- <security:user-service>-->
<!-- <!– SpringSecurity 默认认为密码是加密过得 {noop} 表示后面是不加密的密码内容(5.1.5 版本,对于 4.x 版本 {noop} 并不起作用)–>-->
<!-- <security:user name="sysUser" password="{noop}sysUser" authorities="ROLE_USER"/>-->
<!-- <security:user name="admin" password="{noop}admin" authorities="ROLE_ADMIN"/>-->
<!-- </security:user-service>-->
<!-- </security:authentication-provider>-->
<!-- </security:authentication-manager>-->
<!-- 加密类-->
<bean id="passwordEncoder" class="org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder"/>
<security:authentication-manager>
<!-- 配置用户信息数据服务类-->
<security:authentication-provider user-service-ref="userServiceImpl">
<!-- 配置加密类-->
<security:password-encoder ref="passwordEncoder"/>
</security:authentication-provider>
</security:authentication-manager>
<!-- springSecurity 权限注解开关 :secured-annotations="enabled"-->
<!-- spring 权限注解开关 :pre-post-annotations="enabled"-->
<!-- java250注解支持(java规范的) :jsr250-annotations="enabled"-->
<security:global-method-security
secured-annotations="enabled"
pre-post-annotations="enabled"
jsr250-annotations="enabled"/>
</beans>
自定义账号密码获取方式
User 实现了 UserDetails 接口 ,通过 username 也就是登录 id,从数据库中获取到 user 信息,转换成 SpringSecurity 识别的 UserDetail 并返回,以便 Security 做后续对比
import org.springframework.security.core.userdetails.User;
@Service
public class UserServiceImpl implements UserService {
@Autowired
SysUserDao sysUserDao;
@Override
public UserDetails loadUserByUsername(String s) throws UsernameNotFoundException {
SysUser sysUser = sysUserDao.selectUserByUserName(s);
List<GrantedAuthority> list=new ArrayList<>();
list.add(new SimpleGrantedAuthority("ROLE_USER"));
User user = new User(sysUser.getUsername(), sysUser.getPassword(), list);
return user;
}
}
对应 xml 配置
<security:authentication-manager>
<security:authentication-provider user-service-ref="userServiceImpl">
</security:authentication-provider>
</security:authentication-manager>
自定义加密方式
在 xml 中配置 security:password-encoder ,这里使用的是 Security 提供的 BCryptPasswordEncoder
如果自定义则自写类 实现 PasswordEncoder 接口,security 从前端获取的密码,会使用 PasswordEncoder.matches(CharSequence rawPassword, String encodedPassword) 方法 判断密码是否正确
<bean id="passwordEncoder" class="org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder"/>
<security:authentication-manager>
<!-- 配置用户信息数据服务类-->
<security:authentication-provider user-service-ref="userServiceImpl">
<!-- 配置加密类-->
<security:password-encoder ref="passwordEncoder"/>
</security:authentication-provider>
</security:authentication-manager>
remember me 功能
前端登录提交时加入 remember-me
<form name="f" action="/login" method="post">
用户名:
<input type="text" name="username"/>
<br>
密码:
<input type="text" name="password"/>
<br>
<input type="checkbox" name="remember-me" value="true">
<input type="submit">
<security:csrfInput/>
</form>
security 配置文件
<!-- 开启 remember me 功能 的过滤器 配置有效期 配置数据库源(用于转换token)配置前端传参 name -->
<security:http>
<security:remember-me token-validity-seconds="6000"
data-source-ref="dataSource"
remember-me-parameter="remember-me"/>
</security:http>
记录 token 的表,必须按照这个表格建表,这是 security 要求的
CREATE TABLE `persistent_logins` (
`username` varchar(64) NOT NULL ,
`series` varchar(64) NOT NULL ,
`token` varchar(64) NOT NULL ,
`last_used` timestamp NOT NULL ,
PRIMARY KEY(`series`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;
获取认证对象
SecurityContextHolder : security 的工具类
getContext() :获取 security 的容器
getAuthentication() : 获取 认证对象
SecurityContextHolder.getContext().getAuthentication().getName()
获取用户名
jsp 下可以使用动态标签库
<%@ taglib prefix="security" uri="http://www.springframework.org/security/tags" %>
你好: <%=SecurityContextHolder.getContext().getAuthentication().getName()%> (- - )
<br>
你好2: <security:authentication property="principal.username"/>
<br>
nihao3: <security:authentication property="name"/>
JSP 动态展示菜单标签
如果没有对应角色,则不展示标签内的内容
<%@ taglib prefix="security" uri="http://www.springframework.org/security/tags" %>
<security:authorize access="hasAnyRole('ROLE_PRODUCT')">
<a href="" >产品管理</a>
</security:authorize>
权限注解的使用
- 在配置文件中添加注解支持
<!-- springSecurity 权限注解开关 :secured-annotations="enabled"-->
<!-- spring 权限注解开关 :pre-post-annotations="enabled"-->
<!-- java250注解支持(java规范的) :jsr250-annotations="enabled"-->
<security:global-method-security
secured-annotations="enabled"
pre-post-annotations="enabled"
jsr250-annotations="enabled"/>
需要注意的是:
在 SpringMVC配置文件中 <context:component-scan base-package="com.nothing.controller"/>
Controller 层的 bean,由 SpringMVC 的 IOC 容器管理,所以如果在 Controller 层使用 权限注解,注解支持的配置也应该写在 SpringMVC 的配置文件中
同理: Service 层 是由 Spring 的 IOC 容器管理,如果在Service 层使用权限注解,注解支持的配置要在写在 spring 的配置文件中(如果Spring import 了 SpringSecurity 的配置文件,写在 SpringSecurity 的配置文件里也可以)
-
注解的使用
- Secured : Security 权限注解
- PreAuthorize: Spring 权限注解(spring 的 el 表达式注解)
- RolesAllowed: Java规范权限注解(jsr250)
// 可以在方法上使用 //@Secured({"ROLE_USER"}) @PreAuthorize("hasAnyRole('ROLE_XX')") //@RolesAllowed({"ROLE_XX"}) @RequestMapping("word.do") public String sdas(){} // 也可以在 类上使用 @RestController @Secured({"ROLE_XXX"}) public class TestController {
Java 规范权限注解需要添加依赖
<dependency> <groupId>javax.annotation</groupId> <artifactId>jsr250-api</artifactId> <version>1.0</version> </dependency>
错误处理1
-
简单处理
只能处理 403 异常
</security:http> <security:access-denied-handler error-page="/403.jsp"/> </security:http>
异常处理2
老师讲的很好,看了两三遍。就是异常处理那点感觉有些问题,springSecurity不是基于一些filter来实现的嘛,但是@ControllerAdvice只能拦截到控制器的异常,filter中的异常时拦截不到的,所以还是建议用springSecurity内部的异常处理的机制,springSecurity会抛出两大类异常,一个是AuthenticationException(认证的异常),一个是AccessDeniedException(权限不足的异常),处理的方法时写两个类,一个继承AccessDeniedHandler接口,一个继承AuthenticationEntryPoint接口,然后在springSecurity的配置类configure(HttpSecurity httpSecurity)中配置刚刚写的这两个类,httpSecurity.exceptionHandling().accessDeniedHandler({实现了AccessDeniedHandler接口的类}).authenticationEntryPoint({实现了AuthenticationEntryPoint接口的类})
二、SpringSecurity 整合 SpringBoot 集中式
在SpringBoot 中使用 JSP
- 需要两个核心依赖
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-tomcat</artifactId>
</dependency>
<dependency>
<groupId>org.apache.tomcat.embed</groupId>
<artifactId>tomcat-embed-jasper</artifactId>
</dependency>
注意引入 taglib 否则 类似<%@ taglib prefix="security" uri="http://www.springframework.org/security/tags" %>
这样的标签无法引入使用
<dependency>
<groupId>org.springframework.security</groupId>
<artifactId>spring-security-taglibs</artifactId>
</dependency>
其他如果使用也需要引入
<dependency>
<groupId>javax.servlet</groupId>
<artifactId>jstl</artifactId>
</dependency>
<dependency>
<groupId>javax.servlet</groupId>
<artifactId>javax.servlet-api</artifactId>
</dependency>
-
打包方式 需要改成 war
<packaging>war</packaging>
-
启动方式要通过 maven 命令
mvn spring-boot:run
需要 Build 插件
<build> <plugins> <plugin> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-maven-plugin</artifactId> </plugin> </plugins> </build>
SecurityConfig
写如下配置类
@Configuration
@EnableWebSecurity
// 权限注解启动
@EnableGlobalMethodSecurity(securedEnabled = true,prePostEnabled = true,jsr250Enabled = true)
public class SecurityConfig extends WebSecurityConfigurerAdapter {
@Autowired
// 自己实现这个借口,和 xml 配置法一样
UserService userService;
@Bean
public BCryptPasswordEncoder passwordEncoder(){
return new BCryptPasswordEncoder();
}
@Override
public void configure(AuthenticationManagerBuilder auth) throws Exception {
// 内存加载账户密码防方式
// auth.inMemoryAuthentication().withUser("user").password("{noop}123").roles("USER");
// 使用 自定义 DetailService
auth.userDetailsService(userService).passwordEncoder(passwordEncoder());
}
@Override
public void configure(HttpSecurity http) throws Exception {
// 匹配 /login.jsp 放行
// 匹配 /** 需要角色
// 其他的任何请求 需要授权
// 配置 登录 和 退出 ,登录页面 登录请求地址 等也需要 permitAll
// 最后 不启用csrf
http.authorizeRequests()
.antMatchers("/login2.jsp").permitAll()
.antMatchers("/**").hasAnyRole("USER","ADMIN")
.anyRequest().authenticated()
.and()
.formLogin().loginPage("/login2.jsp").loginProcessingUrl("/login")
.defaultSuccessUrl("/index.jsp")
.failureForwardUrl("/failure.jsp")
.permitAll()
.and()
.logout().logoutUrl("/logout").logoutSuccessUrl("/login2.jsp")
.invalidateHttpSession(true)
.permitAll()
.and()
.csrf().disable();
}
}
三、分布式 SpringSecurity
可以参考此博客:
简单授权:https://www.cnblogs.com/ifme/p/12184433.html
JWT部分:https://www.cnblogs.com/ifme/p/12184587.html
动态授权:https://www.cnblogs.com/fernfei/p/12194847.html
三个工具类
JsonUtils
package com.example.utils;
import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.core.type.TypeReference;
import com.fasterxml.jackson.databind.ObjectMapper;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.io.IOException;
import java.util.List;
import java.util.Map;
/**
* @author: 黑马程序员
**/
public class JsonUtils {
public static final ObjectMapper mapper = new ObjectMapper();
private static final Logger logger = LoggerFactory.getLogger(JsonUtils.class);
//json化
public static String toString(Object obj) {
if (obj == null) {
return null;
}
if (obj.getClass() == String.class) {
return (String) obj;
}
try {
return mapper.writeValueAsString(obj);
} catch (JsonProcessingException e) {
logger.error("json序列化出错:" + obj, e);
return null;
}
}
//json解析
public static <T> T toBean(String json, Class<T> tClass) {
try {
return mapper.readValue(json, tClass);
} catch (IOException e) {
logger.error("json解析出错:" + json, e);
return null;
}
}
//解析list的json数据
public static <E> List<E> toList(String json, Class<E> eClass) {
try {
return mapper.readValue(json, mapper.getTypeFactory().constructCollectionType(List.class, eClass));
} catch (IOException e) {
logger.error("json解析出错:" + json, e);
return null;
}
}
//json转map
public static <K, V> Map<K, V> toMap(String json, Class<K> kClass, Class<V> vClass) {
try {
return mapper.readValue(json, mapper.getTypeFactory().constructMapType(Map.class, kClass, vClass));
} catch (IOException e) {
logger.error("json解析出错:" + json, e);
return null;
}
}
//json解析自定义类型
public static <T> T nativeRead(String json, TypeReference<T> type) {
try {
return mapper.readValue(json, type);
} catch (IOException e) {
logger.error("json解析出错:" + json, e);
return null;
}
}
}
JwtUtils
package com.example.utils;
import com.example.domain.Payload;
import io.jsonwebtoken.Claims;
import io.jsonwebtoken.Jws;
import io.jsonwebtoken.Jwts;
import io.jsonwebtoken.SignatureAlgorithm;
import org.joda.time.DateTime;
import java.security.PrivateKey;
import java.security.PublicKey;
import java.util.Base64;
import java.util.UUID;
/**
* @author: 黑马程序员
* 生成token以及校验token相关方法
*/
public class JwtUtils {
private static final String JWT_PAYLOAD_USER_KEY = "user";
/**
* 私钥加密token
*
* @param userInfo 载荷中的数据
* @param privateKey 私钥
* @param expire 过期时间,单位分钟
* @return JWT
*/
public static String generateTokenExpireInMinutes(Object userInfo, PrivateKey privateKey, int expire) {
return Jwts.builder()
.claim(JWT_PAYLOAD_USER_KEY, JsonUtils.toString(userInfo))
.setId(createJTI())
.setExpiration(DateTime.now().plusMinutes(expire).toDate())
.signWith(privateKey, SignatureAlgorithm.RS256)
.compact();
}
/**
* 私钥加密token
*
* @param userInfo 载荷中的数据
* @param privateKey 私钥
* @param expire 过期时间,单位秒
* @return JWT
*/
public static String generateTokenExpireInSeconds(Object userInfo, PrivateKey privateKey, int expire) {
return Jwts.builder()
.claim(JWT_PAYLOAD_USER_KEY, JsonUtils.toString(userInfo))
.setId(createJTI())
.setExpiration(DateTime.now().plusSeconds(expire).toDate())
.signWith(privateKey, SignatureAlgorithm.RS256)
.compact();
}
/**
* 公钥解析token
*
* @param token 用户请求中的token
* @param publicKey 公钥
* @return Jws<Claims>
*/
private static Jws<Claims> parserToken(String token, PublicKey publicKey) {
return Jwts.parser().setSigningKey(publicKey).parseClaimsJws(token);
}
private static String createJTI() {
return new String(Base64.getEncoder().encode(UUID.randomUUID().toString().getBytes()));
}
/**
* 获取token中的用户信息
*
* @param token 用户请求中的令牌
* @param publicKey 公钥
* @return 用户信息
*/
public static <T> Payload<T> getInfoFromToken(String token, PublicKey publicKey, Class<T> userType) {
Jws<Claims> claimsJws = parserToken(token, publicKey);
Claims body = claimsJws.getBody();
Payload<T> claims = new Payload<>();
claims.setId(body.getId());
claims.setUserInfo(JsonUtils.toBean(body.get(JWT_PAYLOAD_USER_KEY).toString(), userType));
claims.setExpiration(body.getExpiration());
return claims;
}
/**
* 获取token中的载荷信息
*
* @param token 用户请求中的令牌
* @param publicKey 公钥
* @return 用户信息
*/
public static <T> Payload<T> getInfoFromToken(String token, PublicKey publicKey) {
Jws<Claims> claimsJws = parserToken(token, publicKey);
Claims body = claimsJws.getBody();
Payload<T> claims = new Payload<>();
claims.setId(body.getId());
claims.setExpiration(body.getExpiration());
return claims;
}
}
RsaUtils
package com.example.utils;
import java.io.File;
import java.io.IOException;
import java.nio.file.Files;
import java.security.*;
import java.security.spec.InvalidKeySpecException;
import java.security.spec.PKCS8EncodedKeySpec;
import java.security.spec.X509EncodedKeySpec;
import java.util.Base64;
/**
* @author 黑马程序员
*/
public class RsaUtils {
private static final int DEFAULT_KEY_SIZE = 2048;
/**
* 从文件中读取公钥
*
* @param filename 公钥保存路径,相对于classpath
* @return 公钥对象
* @throws Exception
*/
public static PublicKey getPublicKey(String filename) throws Exception {
byte[] bytes = readFile(filename);
return getPublicKey(bytes);
}
/**
* 从文件中读取密钥
*
* @param filename 私钥保存路径,相对于classpath
* @return 私钥对象
* @throws Exception
*/
public static PrivateKey getPrivateKey(String filename) throws Exception {
byte[] bytes = readFile(filename);
return getPrivateKey(bytes);
}
/**
* 获取公钥
*
* @param bytes 公钥的字节形式
* @return
* @throws Exception
*/
private static PublicKey getPublicKey(byte[] bytes) throws Exception {
bytes = Base64.getDecoder().decode(bytes);
X509EncodedKeySpec spec = new X509EncodedKeySpec(bytes);
KeyFactory factory = KeyFactory.getInstance("RSA");
return factory.generatePublic(spec);
}
/**
* 获取密钥
*
* @param bytes 私钥的字节形式
* @return
* @throws Exception
*/
private static PrivateKey getPrivateKey(byte[] bytes) throws NoSuchAlgorithmException, InvalidKeySpecException {
bytes = Base64.getDecoder().decode(bytes);
PKCS8EncodedKeySpec spec = new PKCS8EncodedKeySpec(bytes);
KeyFactory factory = KeyFactory.getInstance("RSA");
return factory.generatePrivate(spec);
}
/**
* 根据密文,生存rsa公钥和私钥,并写入指定文件
*
* @param publicKeyFilename 公钥文件路径
* @param privateKeyFilename 私钥文件路径
* @param secret 生成密钥的密文
*/
public static void generateKey(String publicKeyFilename, String privateKeyFilename, String secret, int keySize) throws Exception {
KeyPairGenerator keyPairGenerator = KeyPairGenerator.getInstance("RSA");
SecureRandom secureRandom = new SecureRandom(secret.getBytes());
keyPairGenerator.initialize(Math.max(keySize, DEFAULT_KEY_SIZE), secureRandom);
KeyPair keyPair = keyPairGenerator.genKeyPair();
// 获取公钥并写出
byte[] publicKeyBytes = keyPair.getPublic().getEncoded();
publicKeyBytes = Base64.getEncoder().encode(publicKeyBytes);
writeFile(publicKeyFilename, publicKeyBytes);
// 获取私钥并写出
byte[] privateKeyBytes = keyPair.getPrivate().getEncoded();
privateKeyBytes = Base64.getEncoder().encode(privateKeyBytes);
writeFile(privateKeyFilename, privateKeyBytes);
}
private static byte[] readFile(String fileName) throws Exception {
return Files.readAllBytes(new File(fileName).toPath());
}
private static void writeFile(String destPath, byte[] bytes) throws IOException {
File dest = new File(destPath);
if (!dest.exists()) {
dest.createNewFile();
}
Files.write(dest.toPath(), bytes);
}
}
pojo类Payload
package com.example.domain;
import lombok.Data;
import java.util.Date;
/**
* @author john
* @date 2020/1/12 - 9:15
*/
@Data
public class Payload<T> {
private String id;
private T userInfo;
private Date expiration;
}
依赖
<dependency>
<groupId>io.jsonwebtoken</groupId>
<artifactId>jjwt-api</artifactId>
<version>0.10.7</version>
</dependency>
<dependency>
<groupId>io.jsonwebtoken</groupId>
<artifactId>jjwt-impl</artifactId>
<version>0.10.7</version>
</dependency>
<dependency>
<groupId>io.jsonwebtoken</groupId>
<artifactId>jjwt-jackson</artifactId>
<version>0.10.7</version>
</dependency>
<dependency>
<groupId>com.fasterxml.jackson.core</groupId>
<artifactId>jackson-databind</artifactId>
<version>2.9.9.3</version>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-logging</artifactId>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<version>1.18.12</version>
</dependency>
<dependency>
<groupId>joda-time</groupId>
<artifactId>joda-time</artifactId>
</dependency>
JWT重写登录逻辑
public class JwtLoginFilter extends UsernamePasswordAuthenticationFilter {
private AuthenticationManager authenticationManager;
private RsaKeyProperties properties;
public JwtLoginFilter(AuthenticationManager authenticationManager, RsaKeyProperties properties) {
this.authenticationManager = authenticationManager;
this.properties = properties;
}
@Override
public Authentication attemptAuthentication(HttpServletRequest request, HttpServletResponse response) throws AuthenticationException {
SysUser sysUser=null;
try {
sysUser = new ObjectMapper().readValue(request.getInputStream(), SysUser.class);
String username = sysUser.getUsername();
String password = sysUser.getPassword();
if (username == null) {
username = "";
}
if (password == null) {
password = "";
}
username = username.trim();
UsernamePasswordAuthenticationToken authRequest = new UsernamePasswordAuthenticationToken(
username, password);
return authenticationManager.authenticate(authRequest);
} catch (Exception e) {
try {
response.setContentType("application/json;charset=utf-8");
response.setStatus(HttpServletResponse.SC_UNAUTHORIZED);
PrintWriter writer = response.getWriter();
HashMap resultMap = new HashMap();
resultMap.put("code",401);
resultMap.put("msg","失败");
writer.write(new ObjectMapper().writeValueAsString(resultMap));
writer.flush();
writer.close();
} catch (IOException ioException) {
ioException.printStackTrace();
}
//e.printStackTrace();
// throw new UsernameNotFoundException("验证失败");
return null;
}
}
@Override
public void successfulAuthentication(HttpServletRequest request, HttpServletResponse response, FilterChain chain, Authentication authResult) throws IOException, ServletException {
SysUser user=new SysUser();
user.setUsername(authResult.getName());
user.setRoles((List<SysRole>) authResult.getAuthorities());
// 获取对象并强转
//SysUser s2 = (SysUser) authResult.getPrincipal();
String token = JwtUtils.generateTokenExpireInMinutes(user, properties.getPrivateKey(), 24 * 60);
response.addHeader("Authorization","Bearer "+token);
try {
response.setContentType("application/json;charset=utf-8");
response.setStatus(HttpServletResponse.SC_OK);
PrintWriter writer = response.getWriter();
HashMap resultMap = new HashMap();
resultMap.put("code",HttpServletResponse.SC_OK);
resultMap.put("msg","认证通过");
writer.write(new ObjectMapper().writeValueAsString(resultMap));
writer.flush();
writer.close();
} catch (IOException ioException) {
ioException.printStackTrace();
}
}
}
JWT重写验证逻辑
public class JwiVerifyFilter extends BasicAuthenticationFilter {
private RsaKeyProperties prop;
public JwiVerifyFilter(AuthenticationManager authenticationManager,RsaKeyProperties prop) {
super(authenticationManager);
this.prop=prop;
}
@Override
public void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain chain) throws IOException, ServletException {
final boolean debug = this.logger.isDebugEnabled();
String header = request.getHeader("Authorization");
if (header == null || !header.startsWith("Bearer ")) {
chain.doFilter(request, response);
response.setContentType("application/json;charset=utf-8");
response.setStatus(HttpServletResponse.SC_OK);
PrintWriter writer = response.getWriter();
HashMap resultMap = new HashMap();
resultMap.put("code",HttpServletResponse.SC_OK);
resultMap.put("msg","请登录");
writer.write(new ObjectMapper().writeValueAsString(resultMap));
writer.flush();
writer.close();
return;
}else {
String token = header.replace("Bearer ", "");
Payload<SysUser> payload = JwtUtils.getInfoFromToken(token, prop.getPublicKey(), SysUser.class);
SysUser user = payload.getUserInfo();
if(user!=null){
UsernamePasswordAuthenticationToken authResult = new UsernamePasswordAuthenticationToken(user.getUsername(), null, user.getRoles());
SecurityContextHolder.getContext().setAuthentication(authResult);
}
chain.doFilter(request, response);
}
}
}
配置类里添加 Filter
http.addFilter(new JwtLoginFilter(super.authenticationManager(),prop))
.addFilter(new JwiVerifyFilter(super.authenticationManager(),prop))
四、动态授权
动态授权:https://www.cnblogs.com/fernfei/p/12194847.html
此人博客比较完整
这里记录几个主要的地方的代码
Security配置类
@Autowired
CustomAccessDecisionManger customAccessDecisionManger;
@Autowired
CustomFilterInvocationSecurityMetadataSource customFilterInvocationSecurityMetadataSource;
@Override
public void configure(HttpSecurity http) throws Exception {
http.authorizeRequests()
.anyRequest().authenticated()
.withObjectPostProcessor(new ObjectPostProcessor<FilterSecurityInterceptor>() {
@Override
public <O extends FilterSecurityInterceptor> O postProcess(O object) {
object.setSecurityMetadataSource(customFilterInvocationSecurityMetadataSource );
object.setAccessDecisionManager(customAccessDecisionManger);
return object;
}
})
}
负责解析 url 对应需要的角色
@Component
public class CustomFilterInvocationSecurityMetadataSource implements FilterInvocationSecurityMetadataSource {
AntPathMatcher antPathMatcher=new AntPathMatcher();
@Autowired
MenuService menuService;
@Override
public Collection<ConfigAttribute> getAttributes(Object object) throws IllegalArgumentException {
String requestUrl = ((FilterInvocation) object).getRequestUrl();
List<Menu> menus = menuService.getAllMenuWithRoles();
for (Menu menu:menus){
if (antPathMatcher.match(requestUrl,menu.getUrl())){
List<Role> roles=menu.getRoles();
String[] roleNames=new String[roles.size()];
for (int i = 0; i < roles.size(); i++) {
roleNames[i]=roles.get(i).getName();
}
return SecurityConfig.createList(roleNames);
}
}
// 给一个缺省角色,表示 url 没有需要的角色
return SecurityConfig.createList("ROLE_DEF");
}
@Override
public Collection<ConfigAttribute> getAllConfigAttributes() {
return null;
}
@Override
public boolean supports(Class<?> clazz) {
return true;
}
}
判断登录的用户是否拥有对应角色
@Component
public class CustomAccessDecisionManger implements AccessDecisionManager {
@Override
public void decide(Authentication authentication, Object object, Collection<ConfigAttribute> configAttributes) throws AccessDeniedException, InsufficientAuthenticationException {
for (ConfigAttribute configAttribute :configAttributes){
if ("ROLE_DEF".equals(configAttribute.getAttribute())){
// 到了这里说明是不需要权限的 url
if (authentication instanceof AnonymousAuthenticationToken){
// 即使不需要权限 也禁止匿名用户访问
// throw new AccessDeniedException("匿名用户 不可以访问");
return;
}else {
// 直接放行
return;
}
}
// authentication 存放了登录的用户的所有信息
Collection<? extends GrantedAuthority> authorities = authentication.getAuthorities();
for (GrantedAuthority authority: authorities){
if (authority.getAuthority().equals(configAttribute.getAttribute())){
return;
}
}
}
throw new AccessDeniedException("权限不足");
}
@Override
public boolean supports(ConfigAttribute attribute) {
return true;
}
@Override
public boolean supports(Class<?> clazz) {
return true;
}
}
网友评论