一、Spring Security介绍
1、框架介绍
Spring 是一个非常流行和成功的 Java 应用开发框架。Spring Security 基于 Spring 框架,提供了一套 Web 应用安全性的完整解决方案。一般来说,Web 应用的安全性包括用户认证**(**Authentication**)和用户授权(**Authorization**)**两个部分。
(1)用户认证指的是:验证某个用户是否为系统中的合法主体,也就是说用户能否访问该系统。用户认证一般要求用户提供用户名和密码。系统通过校验用户名和密码来完成认证过程。
(2)用户授权指的是验证某个用户是否有权限执行某个操作。在一个系统中,不同用户所具有的权限是不同的。比如对一个文件来说,有的用户只能进行读取,而有的用户可以进行修改。一般来说,系统会为不同的用户分配不同的角色,而每个角色则对应一系列的权限。
Spring Security其实就是用filter,多请求的路径进行过滤。
(1)如果是基于Session,那么Spring-security会对cookie里的sessionid进行解析,找到服务器存储的sesion信息,然后判断当前用户是否符合请求的要求。
(2)如果是token,则是解析出token,然后将当前请求加入到Spring-security管理的权限信息中去
2、认证与授权实现思路
如果系统的模块众多,每个模块都需要就行授权与认证,所以我们选择基于token的形式进行授权与认证,用户根据用户名密码认证成功,然后获取当前用户角色的一系列权限值,并以用户名为key,权限列表为value的形式存入redis缓存中,根据用户名相关信息生成token返回,浏览器将token记录到cookie中,每次调用api接口都默认将token携带到header请求头中,Spring-security解析header头获取token信息,解析token获取当前用户名,根据用户名就可以从redis中获取权限列表,这样Spring-security就能够判断当前请求是否有权限访问
![](Spring-Security/01 Spring Security授权过程.png)
二、两个重要接口
1、UserDetailsService
当什么也没有配置的时候,账号和密码是由Spring Security定义生成的。而在实际项目中 账号和密码都是从数据库中查询出来的。 所以我们要通过自定义逻辑控制认证逻辑。
如果需要自定义逻辑时,只需要实现UserDetailsService接口即可。
public interface UserDetailsService {
UserDetails loadUserByUsername(String username) throws UsernameNotFoundException;
}
返回值UserDetails,这个类是系统默认的用户“主体” ,以下是UserDetails接口的抽象方法:
// 表示获取登录用户所有权限
Collection<? extends GrantedAuthority> getAuthorities();
// 表示获取密码
String getPassword();
// 表示获取用户名
String getUsername();
// 表示判断账户是否过期
boolean isAccountNonExpired();
// 表示判断账户是否被锁定
boolean isAccountNonLocked();
// 表示凭证{密码}是否过期
boolean isCredentialsNonExpired();
// 表示当前用户是否可用
boolean isEnabled();
而SpringSecurity里的User类实现了UserDetails,所以一般都是用User类即可
2、PasswordEncoder
// 表示把参数按照特定的解析规则进行解析
String encode(CharSequence rawPassword);
// 表示验证从存储中获取的编码密码与编码后提交的原始密码是否匹配。如果密码匹 配,则返回 true;如果不匹配,则返回 false。第一个参数表示需要被解析的密码。第二个 参数表示存储的密码。
boolean matches(CharSequence rawPassword, String encodedPassword);
// 表示如果解析的密码能够再次进行解析且达到更安全的结果则返回 true,否则返回 false。默认返回false。
default boolean upgradeEncoding(String encodedPassword) { return false; }
BCryptPasswordEncoder是Spring Security官方推荐的密码解析器,平时多使用这个解析 器。
BCryptPasswordEncoder是对bcrypt强散列方法的具体实现。是基于Hash算法实现的单 向加密。可以通过strength控制加密强度,默认10.
@Test public void test01(){ // 创建密码解析器
BCryptPasswordEncoder bCryptPasswordEncoder = new BCryptPasswordEncoder();
// 对密码进行加密
String atguigu = bCryptPasswordEncoder.encode("atguigu");
// 打印加密之后的数据
System.out.println("加密之后数据:\t"+atguigu);
// 判断原字符加密后和加密之前是否匹配
boolean result = bCryptPasswordEncoder.matches("atguigu", atguigu);
// 打印比较结果
System.out.println("比较结果:\t"+result);
}
三、Web项目模块搭建
1、依赖
<!-- Spring Security依赖 -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-security</artifactId>
</dependency>
如果是springboot项目,此时,访问页面就需要登录
2、用户认证
(1)设置密码
方式一
配置文件方式
spring.security.user.name=atguigu
spring.security.user.password=atguigu
方式二
配置类方式
@Configuration
public class SecurityConfig extends WebSecurityConfigurerAdapter {
@Override
protected void configure(AuthenticationManagerBuilder auth) throws Exception {
BCryptPasswordEncoder passwordEncoder = new BCryptPasswordEncoder();
String password = passwordEncoder.encode("123");
auth.inMemoryAuthentication().withUser("lucy").password(password).roles("admin");
}
@Bean
PasswordEncoder password() {
return new BCryptPasswordEncoder();
}
}
方式三(常用)
自定义实现类设置,使用MyBatisPlus查询用户名密码(必须有Mapper和User实体类)
1、创建配置类,设置使用哪个userDetailsService实现类
@Configuration
public class SecurityConfigTest extends WebSecurityConfigurerAdapter {
@Autowired
private UserDetailsService userDetailsService;
//登录
@Override
protected void configure(AuthenticationManagerBuilder auth) throws Exception {
auth.userDetailsService(userDetailsService).passwordEncoder(password());
}
@Bean
PasswordEncoder password() {
return new BCryptPasswordEncoder();
}
}
2、编写实现类,返回User对象,User对象有用户名密码和操作权限
@Service("userDetailsService")
public class MyUserDetailsService implements UserDetailsService {
@Autowired
private UsersMapper usersMapper;
@Override
public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
//调用usersMapper方法,根据用户名查询数据库
QueryWrapper<Users> wrapper = new QueryWrapper();
// where username=?
wrapper.eq("username",username);
Users users = usersMapper.selectOne(wrapper);
//判断
if(users == null) {//数据库没有用户名,认证失败
throw new UsernameNotFoundException("用户名不存在!");
}
//角色必须以ROLE_开头
List<GrantedAuthority> auths = AuthorityUtils.commaSeparatedStringToAuthorityList("admin,ROLE_sale");
//从查询数据库返回users对象,得到用户名和密码,返回
return new User(users.getUsername(),
new BCryptPasswordEncoder().encode(users.getPassword()),auths);
}
}
(2)自定义登录页面
前端提交的表单用户名字段必须是username,密码字段必须是password ,提交方式必须是POST
@Override
protected void configure(HttpSecurity http) throws Exception {
//退出登录
http.logout().logoutUrl("/logout").
logoutSuccessUrl("/test/hello").permitAll();
//配置没有权限访问跳转自定义页面
http.exceptionHandling().accessDeniedPage("/unauth.html");
http.formLogin() //自定义自己编写的登录页面
.loginPage("/on.html") //登录页面设置
.loginProcessingUrl("/user/login") //登录访问路径(表单提交路径),无需创建Controller配置路径,由Security实现
.defaultSuccessUrl("/success.html").permitAll() //登录成功之后,跳转路径
.failureUrl("/unauth.html")
.and().authorizeRequests() //定义那些路径可以直接访问,那些不可以
.antMatchers("/","/test/hello","/user/login").permitAll() //设置哪些路径可以直接访问,不需要认证
.anyRequest().authenticated()
.and().csrf().disable(); //关闭csrf防护
}
3、用户授权
@Override
protected void configure(HttpSecurity http) throws Exception {
http.formLogin() //自定义自己编写的登录页面
.loginPage("/on.html") //登录页面设置
.loginProcessingUrl("/user/login") //登录访问路径(表单提交路径),无需创建Controller配置路径,由Security实现
.defaultSuccessUrl("/success.html").permitAll() //登录成功之后,跳转路径
.failureUrl("/unauth.html") //登录失败跳转页面
.and().authorizeRequests() //定义那些路径可以直接访问,那些不可以
.antMatchers("/","/test/hello","/user/login").permitAll() //设置哪些路径可以直接访问,不需要认证
//----------------------------------------
//当前登录用户,只有具有admins权限才可以访问这个路径
//1 hasAuthority方法,必须拥有参数的所有权限才可以访问
//.antMatchers("/test/index").hasAuthority("admins,manager")
//2 hasAnyAuthority方法,拥有参数的任一权限即可访问
//.antMatchers("/test/index").hasAnyAuthority("admins,manager")
//3 hasRole方法 ROLE_sale
//.antMatchers("/test/index").hasRole("sale")
//.antMatchers("/test/index").hasAnyRole("sale")
.anyRequest().authenticated()
.and().rememberMe().tokenRepository(persistentTokenRepository())
.tokenValiditySeconds(60)//设置有效时长,单位秒
.userDetailsService(userDetailsService);
// .and().csrf().csrfTokenRepository(CookieCsrfTokenRepository.withHttpOnlyFalse());
// .and().csrf().disable(); //关闭csrf防护,默认关闭
}
4、注销和自定义无权访问页
@Override
protected void configure(HttpSecurity http) throws Exception {
//退出
http.logout().logoutUrl("/logout").
logoutSuccessUrl("/test/hello").permitAll();
//配置没有权限访问跳转自定义页面
http.exceptionHandling().accessDeniedPage("/unauth.html");
}
5、自动登录(记住我)
流程
实现
(1)数据库sql
CREATE TABLE `persistent_logins` (
`username` varchar(64) NOT NULL,
`series` varchar(64) NOT NULL,
`token` varchar(64) NOT NULL,
`last_used` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
PRIMARY KEY (`series`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;
(2)配置操作数据库对象
//注入数据源
@Autowired
private DataSource dataSource;
//配置对象
@Bean
public PersistentTokenRepository persistentTokenRepository() {
JdbcTokenRepositoryImpl jdbcTokenRepository = new JdbcTokenRepositoryImpl();
jdbcTokenRepository.setDataSource(dataSource);
//自动建表语句,第一次会自动创建表
//jdbcTokenRepository.setCreateTableOnStartup(true);
return jdbcTokenRepository;
}
(3)配置自动登录
@Autowired private UsersServiceImpl usersService;
@Autowired private PersistentTokenRepository tokenRepository;
// 开启记住我功能
http.rememberMe()
.tokenRepository(tokenRepository)
//默认2周时间。但是可以通过设置状态有效时间,即使项目重新启动下次也可以正常登录。
.tokenValiditySeconds(60) //设置有效时长,单位秒
.userDetailsService(usersService);
(4)前端页面
此处:name 属性值必须位remember-me.不能改为其他值
<input type="checkbox" name="remember-me" title="记住密码"/><br/>
四、微服务模块搭建
1、工具类
(1)token操作
/**
* token操作
*/
public class JwtUtils {
//常量
public static final long EXPIRE = 1000 * 60 * 60 * 24; //token过期时间
public static final String APP_SECRET = "ukc8BDbRigUDaY6pZFfWus2jZWLPHO"; //秘钥
//生成token字符串的方法
public static String getJwtToken(String id, String nickname){
String JwtToken = Jwts.builder()
.setHeaderParam("typ", "JWT")
.setHeaderParam("alg", "HS256")
.setSubject("guli-user")
.setIssuedAt(new Date())
.setExpiration(new Date(System.currentTimeMillis() + EXPIRE))
.claim("id", id) //设置token主体部分 ,存储用户信息
.claim("nickname", nickname)
.signWith(SignatureAlgorithm.HS256, APP_SECRET)
.compact();
return JwtToken;
}
/**
* 判断token是否存在与有效
* @param jwtToken
* @return
*/
public static boolean checkToken(String jwtToken) {
if(StringUtils.isEmpty(jwtToken)) return false;
try {
Jwts.parser().setSigningKey(APP_SECRET).parseClaimsJws(jwtToken);
} catch (Exception e) {
e.printStackTrace();
return false;
}
return true;
}
/**
* 判断token是否存在与有效
* @param request
* @return
*/
public static boolean checkToken(HttpServletRequest request) {
try {
String jwtToken = request.getHeader("token");
if(StringUtils.isEmpty(jwtToken)) return false;
Jwts.parser().setSigningKey(APP_SECRET).parseClaimsJws(jwtToken);
} catch (Exception e) {
e.printStackTrace();
return false;
}
return true;
}
/**
* 根据token字符串获取会员id
* @param request
* @return
*/
public static String getMemberIdByJwtToken(HttpServletRequest request) {
String jwtToken = request.getHeader("token");
if(StringUtils.isEmpty(jwtToken)) return "";
Jws<Claims> claimsJws = Jwts.parser().setSigningKey(APP_SECRET).parseClaimsJws(jwtToken);
Claims claims = claimsJws.getBody();
return (String)claims.get("id");
}
}
(2)MD5加密
/*
* MD5加密工具类
* */
public final class MD5 {
public static String encrypt(String strSrc) {
try {
char hexChars[] = { '0', '1', '2', '3', '4', '5', '6', '7', '8',
'9', 'a', 'b', 'c', 'd', 'e', 'f' };
byte[] bytes = strSrc.getBytes();
MessageDigest md = MessageDigest.getInstance("MD5");
md.update(bytes);
bytes = md.digest();
int j = bytes.length;
char[] chars = new char[j * 2];
int k = 0;
for (int i = 0; i < bytes.length; i++) {
byte b = bytes[i];
chars[k++] = hexChars[b >>> 4 & 0xf];
chars[k++] = hexChars[b & 0xf];
}
return new String(chars);
} catch (NoSuchAlgorithmException e) {
e.printStackTrace();
throw new RuntimeException("MD5加密出错!!+" + e);
}
}
public static void main(String[] args) {
System.out.println(MD5.encrypt("111111"));
}
}
(3)Response返回数据
/*
* 通过response返回数据
* */
public class ResponseUtil {
public static void out(HttpServletResponse response, R r) {
ObjectMapper mapper = new ObjectMapper();
response.setStatus(HttpStatus.OK.value());
response.setContentType(MediaType.APPLICATION_JSON_UTF8_VALUE);
try {
mapper.writeValue(response.getWriter(), r);
} catch (IOException e) {
e.printStackTrace();
}
}
}
(4)自定义返回数据类型
/*
* 统一返回结果
* */
@Data
public class R {
@ApiModelProperty(value = "是否成功")
private Boolean success;
@ApiModelProperty(value = "返回码")
private Integer code;
@ApiModelProperty(value = "返回消息")
private String message;
@ApiModelProperty(value = "返回数据")
private Map<String, Object> data = new HashMap<String, Object>();
//私有化空参构造,阻止外界调用构造方法
private R(){}
//返回值类型为本身R,可提供链式编程
//成功静态方法
public static R ok() {
R r = new R();
r.setSuccess(true);
r.setCode(ResultCode.SUCCESS);
r.setMessage("成功");
return r;
}
//失败静态方法
public static R error() {
R r = new R();
r.setSuccess(false);
r.setCode(ResultCode.ERROR);
r.setMessage("失败");
return r;
}
public R success(Boolean success){
this.setSuccess(success);
return this;
}
public R message(String message){
this.setMessage(message);
return this;
}
public R code(Integer code){
this.setCode(code);
return this;
}
public R data(String key, Object value){
this.data.put(key, value);
return this;
}
public R data(Map<String, Object> map){
this.setData(map);
return this;
}
}
(5)RedisTemplate
@EnableCaching //开启缓存
@Configuration //配置类
public class RedisConfig extends CachingConfigurerSupport {
@Bean
public RedisTemplate<String, Object> redisTemplate(RedisConnectionFactory factory) {
RedisTemplate<String, Object> template = new RedisTemplate<>();
RedisSerializer<String> redisSerializer = new StringRedisSerializer();
Jackson2JsonRedisSerializer jackson2JsonRedisSerializer = new Jackson2JsonRedisSerializer(Object.class);
//ObjectMapper 指定在转成json的时候的一些转换规则
ObjectMapper om = new ObjectMapper();
om.setVisibility(PropertyAccessor.ALL, JsonAutoDetect.Visibility.ANY);
om.enableDefaultTyping(ObjectMapper.DefaultTyping.NON_FINAL);
//把自定义objectMapper设置到jackson2JsonRedisSerializer中(可以不设置,使用默认规则)
jackson2JsonRedisSerializer.setObjectMapper(om);
template.setConnectionFactory(factory);
//key序列化方式
template.setKeySerializer(redisSerializer);
//value序列化
template.setValueSerializer(jackson2JsonRedisSerializer);
//value hashmap序列化
template.setHashValueSerializer(jackson2JsonRedisSerializer);
return template;
}
@Bean
public CacheManager cacheManager(RedisConnectionFactory factory) {
RedisSerializer<String> redisSerializer = new StringRedisSerializer();
Jackson2JsonRedisSerializer jackson2JsonRedisSerializer = new Jackson2JsonRedisSerializer(Object.class);
//解决查询缓存转换异常的问题
ObjectMapper om = new ObjectMapper();
om.setVisibility(PropertyAccessor.ALL, JsonAutoDetect.Visibility.ANY);
om.enableDefaultTyping(ObjectMapper.DefaultTyping.NON_FINAL);
jackson2JsonRedisSerializer.setObjectMapper(om);
// 配置序列化(解决乱码的问题),过期时间600秒
RedisCacheConfiguration config = RedisCacheConfiguration.defaultCacheConfig()
.entryTtl(Duration.ofSeconds(600))
.serializeKeysWith(RedisSerializationContext.SerializationPair.fromSerializer(redisSerializer))
.serializeValuesWith(RedisSerializationContext.SerializationPair.fromSerializer(jackson2JsonRedisSerializer))
.disableCachingNullValues();
RedisCacheManager cacheManager = RedisCacheManager.builder(factory)
.cacheDefaults(config)
.build();
return cacheManager;
}
}
2、自定义security功能
(1)自定义密码的处理方法
@Component
public class DefaultPasswordEncoder implements PasswordEncoder {
public DefaultPasswordEncoder() {
this(-1);
}
/**
* @param strength
* the log rounds to use, between 4 and 31
*/
public DefaultPasswordEncoder(int strength) {
}
//对密码进行加密
public String encode(CharSequence rawPassword) {
return MD5.encrypt(rawPassword.toString());
}
//加密字符串和未加密字符串进行比较
public boolean matches(CharSequence rawPassword, String encodedPassword) {
return encodedPassword.equals(MD5.encrypt(rawPassword.toString()));
}
}
(2)登出业务逻辑类
/**
* 登出业务逻辑类
*/
public class TokenLogoutHandler implements LogoutHandler {
private TokenManager tokenManager;
private RedisTemplate redisTemplate;
public TokenLogoutHandler(TokenManager tokenManager, RedisTemplate redisTemplate) {
this.tokenManager = tokenManager;
this.redisTemplate = redisTemplate;
}
@Override
public void logout(HttpServletRequest request, HttpServletResponse response, Authentication authentication) {
//从header获取token,若token不为空,则移除token,并从redis删除。
String token = request.getHeader("token");
if (token != null) {
tokenManager.removeToken(token);
//清空当前用户缓存中的权限数据
String userName = tokenManager.getUserFromToken(token);
redisTemplate.delete(userName);
}
ResponseUtil.out(response, R.ok());
}
}
(3)token管理
@Component
public class TokenManager {
//token的有效时间
private long tokenExpiration = 24*60*60*1000;
//编码秘钥
private String tokenSignKey = "123456";
//根据用户名生成token
public String createToken(String username) {
String token = Jwts.builder().setSubject(username)
.setExpiration(new Date(System.currentTimeMillis() + tokenExpiration))
.signWith(SignatureAlgorithm.HS512, tokenSignKey).compressWith(CompressionCodecs.GZIP).compact();
return token;
}
//根据token的到用户信息
public String getUserFromToken(String token) {
String user = Jwts.parser().setSigningKey(tokenSignKey).parseClaimsJws(token).getBody().getSubject();
return user;
}
public void removeToken(String token) {
//jwttoken无需删除,客户端扔掉即可。
}
}
(4)未授权的统一处理方式
/**
* 未授权的统一处理方式
*/
public class UnauthorizedEntryPoint implements AuthenticationEntryPoint {
@Override
public void commence(HttpServletRequest request, HttpServletResponse response,
AuthenticationException authException) throws IOException, ServletException {
ResponseUtil.out(response, R.error());
}
}
3、实体类
(1)User
根据表结构生成
一个对象序列化的接口,一个类只有实现了Serializable接口,它的对象才能被序列化。
@Data
@ApiModel(description = "用户实体类")
public class User implements Serializable {
private static final long serialVersionUID = 1L;
@ApiModelProperty(value = "微信openid")
private String username;
@ApiModelProperty(value = "密码")
private String password;
@ApiModelProperty(value = "昵称")
private String nickName;
@ApiModelProperty(value = "用户头像")
private String salt;
@ApiModelProperty(value = "用户签名")
private String token;
}
(2)SecurityUser
最终交给Spring Security的是UserDetails
。该接口是提供用户信息的核心接口。该接口实现仅仅存储用户的信息。后续会将该接口提供的用户信息封装到认证对象Authentication
中去。UserDetails
默认提供了:
- 用户的权限集, 默认需要添加
ROLE_
前缀 - 用户的加密后的密码, 不加密会使用
{noop}
前缀 - 应用内唯一的用户名
- 账户是否过期
- 账户是否锁定
- 凭证是否过期
- 用户是否可用
如果以上的信息满足不了你使用,你可以自行实现扩展以存储更多的用户信息。比如用户的邮箱、手机号等等。通常我们使用其实现类:
org.springframework.security.core.userdetails.User
该类内置一个建造器UserBuilder
会很方便地帮助我们构建UserDetails
对象,后面我们会用到它。
@Data
@Slf4j
public class SecurityUser implements UserDetails {
//当前登录用户
private transient User currentUserInfo;
//当前权限
private List<String> permissionValueList;
public SecurityUser() {
}
public SecurityUser(User user) {
if (user != null) {
this.currentUserInfo = user;
}
}
@Override
public Collection<? extends GrantedAuthority> getAuthorities() {
Collection<GrantedAuthority> authorities = new ArrayList<>();
for(String permissionValue : permissionValueList) {
if(StringUtils.isEmpty(permissionValue)) continue;
SimpleGrantedAuthority authority = new SimpleGrantedAuthority(permissionValue);
authorities.add(authority);
}
return authorities;
}
@Override
public String getPassword() {
return currentUserInfo.getPassword();
}
@Override
public String getUsername() {
return currentUserInfo.getUsername();
}
@Override
public boolean isAccountNonExpired() {
return true;
}
@Override
public boolean isAccountNonLocked() {
return true;
}
@Override
public boolean isCredentialsNonExpired() {
return true;
}
@Override
public boolean isEnabled() {
return true;
}
}
4、UserDetailsService
/**
* 自定义userDetailsService - 认证用户详情
* 查询数据库获取用户密码和权限
*/
@Service("userDetailsService")
public class UserDetailsServiceImpl implements UserDetailsService {
@Autowired
private UserService userService;
@Autowired
private PermissionService permissionService;
/***
* 根据账号获取用户信息
* @param username:
* @return: org.springframework.security.core.userdetails.UserDetails
*/
@Override
public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
// 从数据库中取出用户信息
User user = userService.selectByUsername(username);
// 判断用户是否存在
if (null == user){
//throw new UsernameNotFoundException("用户名不存在!");
}
// 返回UserDetails实现类
com.rewind.security.entity.User curUser = new com.rewind.security.entity.User();
BeanUtils.copyProperties(user,curUser);
List<String> authorities = permissionService.selectPermissionValueByUserId(user.getId());
//自定义的实现UserDetails的实体类
SecurityUser securityUser = new SecurityUser(curUser);
securityUser.setCurrentUserInfo(curUser);
securityUser.setPermissionValueList(authorities);
return securityUser;
}
}
5、核心配置类
@Configuration
@EnableWebSecurity
@EnableGlobalMethodSecurity(prePostEnabled = true)
public class TokenWebSecurityConfig extends WebSecurityConfigurerAdapter {
//自己写的service的实现类,用于springsecurity查询数据库
private UserDetailsService userDetailsService;
private TokenManager tokenManager;
private DefaultPasswordEncoder defaultPasswordEncoder;
private RedisTemplate redisTemplate;
@Autowired
public TokenWebSecurityConfig(UserDetailsService userDetailsService, DefaultPasswordEncoder defaultPasswordEncoder,
TokenManager tokenManager, RedisTemplate redisTemplate) {
this.userDetailsService = userDetailsService;
this.defaultPasswordEncoder = defaultPasswordEncoder;
this.tokenManager = tokenManager;
this.redisTemplate = redisTemplate;
}
/**
* 配置设置
* @param http
* @throws Exception
*/
@Override
protected void configure(HttpSecurity http) throws Exception {
http.exceptionHandling()
.authenticationEntryPoint(new UnauthorizedEntryPoint())
.and().csrf().disable()
.authorizeRequests()
.anyRequest().authenticated()
.and().logout().logoutUrl("/admin/acl/index/logout")//退出登录路径
.addLogoutHandler(new TokenLogoutHandler(tokenManager,redisTemplate)).and()//自定义退出处理器
.addFilter(new TokenLoginFilter(authenticationManager(), tokenManager, redisTemplate))//自定义登录过滤器
.addFilter(new TokenAuthenticationFilter(authenticationManager(), tokenManager, redisTemplate)).httpBasic();//自定义权限过滤器
}
/**
* 密码处理
* @param auth
* @throws Exception
*/
@Override
public void configure(AuthenticationManagerBuilder auth) throws Exception {
auth.userDetailsService(userDetailsService).passwordEncoder(defaultPasswordEncoder);
}
/**
* 配置哪些请求不拦截
* @param web
* @throws Exception
*/
@Override
public void configure(WebSecurity web) throws Exception {
// web.ignoring().antMatchers("/api/**",
// "/swagger-resources/**", "/webjars/**", "/v2/**", "/swagger-ui.html/**"
// );
web.ignoring().antMatchers("/*/**");
}
}
6、代码中获取用户
//获取当前登录用户用户名
String username = SecurityContextHolder.getContext().getAuthentication().getName();
五、常用注解
1、@Secured角色
判断是否具有角色,不具有无法访问,另外需要注意的是这里匹配的字符串需要添加前缀“ROLE_“。
使用注解先要开启注解功能! (启动类上添加以下注解)
@EnableGlobalMethodSecurity(securedEnabled=true)
在控制器方法上使用注解
// 测试注解:
@RequestMapping("testSecured")
@ResponseBody
@Secured({"ROLE_normal","ROLE_admin"})
public String helloUser() {
return "hello,user";
}
2、@PreAuthorize
@PreAuthorize:注解适合进入方法前的权限验证, @PreAuthorize 可以将登录用 户的roles/permissions 参数传到方法中。
使用注解先要开启注解功能! (启动类上添加以下注解)
@EnableGlobalMethodSecurity(prePostEnabled = true)
@RequestMapping("/preAuthorize")
@ResponseBody
//@PreAuthorize("hasRole('ROLE_ 管理员 ')")
@PreAuthorize("hasAnyAuthority('menu:system')")
public String preAuthorize(){
System.out.println("preAuthorize");
return "preAuthorize";
}
3、@PostAuthorize
@PostAuthorize 注解使用并不多,在方法执行后再进行权限验证,适合验证带有返回值 的权限.
使用注解先要开启注解功能! (启动类上添加以下注解)
@EnableGlobalMethodSecurity(prePostEnabled = true)
@RequestMapping("/testPostAuthorize")
@ResponseBody
@PostAuthorize("hasAnyAuthority('menu:system')")
public String preAuthorize(){
System.out.println("test--PostAuthorize");
return "PostAuthorize";
}
4、@PostFilter
@PostFilter :权限验证之后对数据进行过滤 留下用户名是admin1的数据
表达式中的 filterObject 引用的是方法返回值List中的某一个元素
@RequestMapping("getAll")
@PreAuthorize("hasRole('ROLE_管理员')")
@PostFilter("filterObject.username == 'admin1'")
@ResponseBody
public List<UserInfo> getAllUser(){
ArrayList<UserInfo> list = new ArrayList<>();
list.add(new UserInfo(1l,"admin1","6666"));
list.add(new UserInfo(2l,"admin2","888"));
return list;
}
5、@PreFilter
@PreFilter: 进入控制器之前对数据进行过滤
@RequestMapping("getTestPreFilter")
@PreAuthorize("hasRole('ROLE_管理员')")
@PreFilter(value = "filterObject.id%2==0")
@ResponseBody
public List<UserInfo> getTestPreFilter(@RequestBody List<UserInfo> list){
list.forEach(t-> {
System.out.println(t.getId()+"\t"+t.getUsername());
});
return list;
}