留个笔记,以后自己可以看看,spring-security 的配置demo,会陆续补充
idea
maven
<dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-security</artifactId> </dependency>
引入后,写个RestController
@RestController
public class HelloController {
@RequestMapping("/hello")
String home() {
return "Hello ,spring security!";
}
}
启动Spring-boot,发现控制台有个东西
输入:http://localhost:8080/hello,发现跳转到一个登录页:http://localhost:8080/login
账号是:user,密码是控制台上的security password
跳转过了,神奇。
现在没有任何的配置,只要输入账号密码用了,现在干下配置
@Configuration
@EnableWebSecurity
public class SecurityConfiguration extends WebSecurityConfigurerAdapter {
//设置密码加密策略,这里使用明文密码,即无加密策略
@Bean
PasswordEncoder passwordEncoder(){
return NoOpPasswordEncoder.getInstance();
}
@Override
public void configure(AuthenticationManagerBuilder auth) throws Exception {
auth.inMemoryAuthentication().passwordEncoder(passwordEncoder())//声明密码策略
.withUser("spring") .password("123456").roles("USER")//内存创建用户、权限
.and()
.withUser("admin").password("123456").roles("ADMIN", "USER");
}
@Override
protected void configure(HttpSecurity http) throws Exception {
http
.authorizeRequests()// 定义哪些URL需要被保护、哪些不需要被保护
.antMatchers("/product/**").hasRole("USER")//部分请求需要某个角色才能访问
.antMatchers("/admin/**").hasRole("ADMIN")
.antMatchers("/hello*").hasRole("ADMIN")
.anyRequest().authenticated()// 任何请求,登录后可以访问
.and().httpBasic()//http支持基础认证
. and().formLogin()// 定义当需要用户登录时候,转到的登录页面。默认是spring-security自带的
.successForwardUrl("/hello")//登录成功默认跳转的地址
;
}
@Override
public void configure(WebSecurity web) throws Exception {
web.ignoring().antMatchers("/vercode");//忽略某些地址
}
}
controller写法:
@RestController
public class HelloController {
@RequestMapping("/hello")
String home() {
return "Hello ,spring security!";
}
@RequestMapping("/vercode")
String vercode() {
return "Hello ,vercode security!";
}
@RequestMapping("/product/info")
public String productInfo(){
return " some product info ";
}
@RequestMapping("/admin/home")
public String adminInfo(){
return " admin home page ";
}
}
启动项目:
http://localhost:8080/vercode,直接运行成功不需要登录
http://localhost:8080/ 跳出登录页面,输入密码后,就可以运行成功了(spring账户登录)
http://localhost:8080/hello 出现403,http://localhost:8080/product/info成功访问
下面试试,走数据库获取账号密码
数据库随便创建个用户表
创建用户实体,实现框架用户对象,这里只简单对账号密码进行测试学习
public class AisinoUser implements UserDetails {
private String username;
private String userpassword;
public void setUsername(String username) {
this.username = username;
}
public String getUserpassword() {
return userpassword;
}
public void setUserpassword(String userpassword) {
this.userpassword = userpassword;
}
@Override
public Collection<? extends GrantedAuthority> getAuthorities() {
return null;
}
@Override
public String getPassword() {
return this.userpassword;
}
@Override
public String getUsername() {
return this.username;
}
//账户是否过期
@Override
public boolean isAccountNonExpired() {
return true;
}
//账户是否被锁
@Override
public boolean isAccountNonLocked() {
return true;
}
//密码是否过期
@Override
public boolean isCredentialsNonExpired() {
return true;
}
//账户是否可用
@Override
public boolean isEnabled() {
return true;
}
}
框架的用户有了,再实现框架那块判定用户的代码,实现UserDetailsService,重写loadUserByUsername方法
@Service
public class UserDetailServiceImpl implements UserDetailsService {
@Autowired
UserDetailDao dao;
@Override
public UserDetails loadUserByUsername(String userName) throws UsernameNotFoundException {
AisinoUser userByUsername = dao.getUserByUsername(userName);
if(userByUsername==null){
throw new UsernameNotFoundException("用户未找到");
}
return userByUsername;
}
}
自己写个查找用户的dao
@Mapper
public interface UserDetailDao {
AisinoUser getUserByUsername(String username);
}
<mapper namespace="com.example.demo.hello.dao.UserDetailDao">
<select id="getUserByUsername" resultType="com.example.demo.hello.bo.AisinoUser">
select
*
from aisinouser
where username=#{username};
</select>
</mapper>
配置那边改改,当时用的内存载入密码,这次通过数据库查询密码
@Configuration
@EnableWebSecurity
public class SecurityConfiguration extends WebSecurityConfigurerAdapter {
@Autowired
UserDetailServiceImpl service;
//设置密码加密策略,这里使用明文密码,即无加密策略
@Bean
PasswordEncoder passwordEncoder(){
return NoOpPasswordEncoder.getInstance();
}
@Override
public void configure(AuthenticationManagerBuilder auth) throws Exception {
// auth.inMemoryAuthentication().passwordEncoder(passwordEncoder())//声明密码策略
// .withUser("spring") .password("123456").roles("USER")//内存创建用户、权限
// .and()
// .withUser("admin").password("123456").roles("ADMIN", "USER");
auth.userDetailsService(service);
}
@Override
protected void configure(HttpSecurity http) throws Exception {
http
.authorizeRequests()// 定义哪些URL需要被保护、哪些不需要被保护
.antMatchers("/product/**").hasRole("USER")//部分请求需要某个角色才能访问
.antMatchers("/admin/**").hasRole("ADMIN")
.antMatchers("/hello*").authenticated()
.anyRequest().authenticated()// 任何请求,登录后可以访问
.and().httpBasic()//http支持基础认证
. and().formLogin()// 定义当需要用户登录时候,转到的登录页面。默认是spring-security自带的
// .successForwardUrl("/hello")//登录成功默认跳转的地址
;
}
@Override
public void configure(WebSecurity web) throws Exception {
web.ignoring().antMatchers("/vercode");//忽略某些地址
}
}
输入localhost:8080 跳转到登录页面,账号密码输入1,出现404,说明成功
输入localhost:8080/hello,跳转正常
输入localhost:8080/product/info,出现type=Forbidden, status=403正常,说明没有赋予角色
后续参考:https://blog.csdn.net/zhaoxichen_10/article/details/88713799
发现好文:https://www.cnblogs.com/zhengqing/p/11704229.html?utm_source=tuicool&utm_medium=referral
赋予用户角色:
数据库写好部分角色,这边角色大小写要注意,默认比较角色的时候用equals
mybatis写好:
<select id="getUserRoles" resultType="com.example.demo.hello.bo.UserRoles">
select
*
from user_role
where username=#{username};
</select>
在loadUserByUsername那个认证方法里面,加上角色信息,方法完整的写法:
@Override
public UserDetails loadUserByUsername(String userName) throws UsernameNotFoundException {
AisinoUser userByUsername = dao.getUserByUsername(userName);
if(userByUsername==null){
throw new UsernameNotFoundException("用户未找到");
}
List<UserRoles> userRoles = dao.getUserRoles(userName);
if (userRoles!=null && userRoles.size()>0) {
List<GrantedAuthority> grantedAuthorities = new ArrayList<>(userRoles.size());
for (UserRoles role : userRoles) {
//框架角色以ROLE_开头的,所以我们先加上
grantedAuthorities.add(new SimpleGrantedAuthority("ROLE_" + role.getRole()));
}
userByUsername.setAuthorities(grantedAuthorities);
}
return userByUsername;
}
配置那边,还是先用内存模式配置角色
http
.authorizeRequests()
.antMatchers("/product/**").hasRole("user")//部分请求需要某个角色才能访问,角色大小写要注意
.antMatchers("/admin/**").hasRole("admin1")
.antMatchers("/hello*").authenticated()
.anyRequest().authenticated()// 任何请求,登录后可以访问
.and().httpBasic()//http支持基础认证
. and().formLogin()// 定义当需要用户登录时候,转到的登录页面。默认是spring-security自带的
;
启动服务试试,登录后,走/hello
走product/info
走admin/home
正常
整完这个,下面不满足地址、角色权限是固定值写在配置文件里面,那么就动态配置咯
后续参考:https://blog.csdn.net/zhaoxichen_10/article/details/88713799
发现好文:https://www.cnblogs.com/zhengqing/p/11704229.html?utm_source=tuicool&utm_medium=referral
核心:重写实现FilterInvocationSecurityMetadataSource,重写getAttributes()
方法 获取访问该url所需要的角色权限信息;重写实现AccessDecisionManager,重写decide()
方法 对访问url进行权限认证处理,上代码:
package com.example.demo.hello.config;
import com.example.demo.hello.bo.MenuRoles;
import com.example.demo.hello.dao.UserDetailDao;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.security.access.ConfigAttribute;
import org.springframework.security.access.SecurityConfig;
import org.springframework.security.web.FilterInvocation;
import org.springframework.security.web.access.intercept.FilterInvocationSecurityMetadataSource;
import org.springframework.stereotype.Component;
import org.springframework.util.AntPathMatcher;
import javax.servlet.http.HttpServletRequest;
import java.util.*;
@Component
public class MyFilterInvocationSecurityMetadataSource
implements FilterInvocationSecurityMetadataSource
{
@Autowired
UserDetailDao dao;
private AntPathMatcher antPathMatcher = new AntPathMatcher();
/**
动态加载此链接需要什么权限,权限可以写在数据库,但不能决策,只能查询,查询完了,给决策使用
*/
public Collection<ConfigAttribute> getAttributes(Object object) {
HttpServletRequest request = ((FilterInvocation)object).getRequest();
String requestUrl =request.getRequestURI();
List<MenuRoles> allMenu = dao.getMenuRoles();
List<ConfigAttribute> attributes = new ArrayList();
for (MenuRoles menu : allMenu) {
if (antPathMatcher.match(menu.getMenu_url(), requestUrl)) {
SecurityConfig securityConfig = new SecurityConfig("ROLE_"+menu.getMenu_role().trim());
attributes.add(securityConfig);
}
}
if(attributes.size()==0){
return SecurityConfig.createList("ROLE_LOGIN");
}
return attributes;
}
/**
* @Author: Galen
* @Description: 此处方法如果做了实现,返回了定义的权限资源列表,
* Spring Security会在启动时校验每个ConfigAttribute是否配置正确,
* 如果不需要校验,这里实现方法,方法体直接返回null即可。
* @Date: 2019/3/27-17:12
* @Param: []
* @return: java.util.Collection<org.springframework.security.access.ConfigAttribute>
**/
@Override
public Collection<ConfigAttribute> getAllConfigAttributes() {
return null;
}
@Override
public boolean supports(Class<?> aClass) {
return true;
}
}
package com.example.demo.hello.config;
import org.springframework.security.access.AccessDecisionManager;
import org.springframework.security.access.AccessDeniedException;
import org.springframework.security.access.ConfigAttribute;
import org.springframework.security.authentication.AnonymousAuthenticationToken;
import org.springframework.security.authentication.BadCredentialsException;
import org.springframework.security.authentication.InsufficientAuthenticationException;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.GrantedAuthority;
import org.springframework.security.core.context.SecurityContext;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.stereotype.Component;
import java.util.Collection;
import java.util.Iterator;
/**
* 决策器
*/
/**
* @Author: Galen
* @Date: 2019/3/27-16:59
* @Description: Decision决定的意思。
* 有了权限资源(MyFilterInvocationSecurityMetadataSource),知道了当前访问的url需要的具体权限,接下来就是决策当前的访问是否能通过权限验证了
* MyAccessDecisionManager 自定义权限决策管理器
**/
@Component
public class MyAccessDecisionManager implements AccessDecisionManager {
/**
* @Author: Galen
* @Description: 取当前用户的权限与这次请求的这个url需要的权限作对比,决定是否放行
* auth 包含了当前的用户信息,包括拥有的权限,即之前UserDetailsService登录时候存储的用户对象
* object 就是FilterInvocation对象,可以得到request等web资源。
* configAttributes 是本次访问需要的权限。即上一步的 MyFilterInvocationSecurityMetadataSource 中查询核对得到的权限列表
* @Date: 2019/3/27-17:18
* @Param: [auth, object, cas]
* @return: void
**/
@Override
public void decide(Authentication auth, Object object, Collection<ConfigAttribute> configAttributes) throws AccessDeniedException, InsufficientAuthenticationException {
Iterator<ConfigAttribute> iterator = configAttributes.iterator();
while (iterator.hasNext()) {
if (auth == null) {
throw new AccessDeniedException("当前访问没有权限");
}
ConfigAttribute ca = iterator.next();
//当前请求需要的权限,在自定义MyFilterInvocationSecurityMetadataSource .getAttributes 中返回的
String needRole = ca.getAttribute();
if ("ROLE_LOGIN".equals(needRole)) {//默认此页面不需要权限登录,判定用户是否登录,没登录走登录,否则,房型
if (auth instanceof AnonymousAuthenticationToken) {
throw new BadCredentialsException("未登录");
} else
return;
}
//
SecurityContext context = SecurityContextHolder.getContext();
Authentication authentication = context.getAuthentication();
String username = authentication.getName();
Object principal = authentication.getPrincipal();
// Collection<? extends GrantedAuthority> authorities = authentication.getAuthorities();
//当前用户所具有的权限
Collection<? extends GrantedAuthority> authorities = authentication.getAuthorities();
for (GrantedAuthority authority : authorities) {
if (authority.getAuthority().equals(needRole)) {
return;
}
}
}
throw new AccessDeniedException("权限不足!");
}
@Override
public boolean supports(ConfigAttribute configAttribute) {
return false;
}
@Override
public boolean supports(Class<?> aClass) {
return false;
}
}
package com.example.demo.hello.dao;
import com.example.demo.hello.bo.AisinoUser;
import com.example.demo.hello.bo.MenuRoles;
import com.example.demo.hello.bo.UserRoles;
import org.apache.ibatis.annotations.Mapper;
import java.util.List;
@Mapper
public interface UserDetailDao {
AisinoUser getUserByUsername(String username);
/**
* 获取用户角色
* @param username
* @return
*/
List< UserRoles> getUserRoles(String username);
/**
* 获取菜单需要的权限
* @return
*/
List<MenuRoles> getMenuRoles();
}
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.example.demo.hello.dao.UserDetailDao">
<select id="getUserByUsername" resultType="com.example.demo.hello.bo.AisinoUser">
select
*
from aisinouser
where username=#{username};
</select>
<select id="getUserRoles" resultType="com.example.demo.hello.bo.UserRoles">
select
*
from user_role
where username=#{username};
</select>
<select id="getMenuRoles" resultType="com.example.demo.hello.bo.MenuRoles">
select
*
from menu_role
</select>
</mapper>
security核心配置
package com.example.demo.hello.config;
import com.example.demo.hello.service.UserDetailServiceImpl;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.authentication.AuthenticationManager;
import org.springframework.security.config.annotation.ObjectPostProcessor;
import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.builders.WebSecurity;
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.crypto.password.NoOpPasswordEncoder;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.security.web.access.intercept.FilterSecurityInterceptor;
import org.springframework.security.web.authentication.AuthenticationFailureHandler;
import org.springframework.security.web.authentication.AuthenticationSuccessHandler;
import org.springframework.security.web.authentication.logout.LogoutSuccessHandler;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.io.PrintWriter;
@Configuration
@EnableWebSecurity
public class SecurityConfiguration extends WebSecurityConfigurerAdapter {
@Autowired
UserDetailServiceImpl service;
//设置密码加密策略,这里使用明文密码,即无加密策略
@Autowired
private MyFilterInvocationSecurityMetadataSource filterMetadataSource; //权限过滤器(当前url所需要的访问权限)
@Autowired
private MyAccessDecisionManager myAccessDecisionManager;//权限决策器
@Bean
PasswordEncoder passwordEncoder(){
return NoOpPasswordEncoder.getInstance();
}
@Override
public void configure(AuthenticationManagerBuilder auth) throws Exception {
// auth.inMemoryAuthentication().passwordEncoder(passwordEncoder())//声明密码策略
// .withUser("spring") .password("123456").roles("USER")//内存创建用户、权限
// .and()
// .withUser("admin").password("123456").roles("ADMIN", "USER");
auth.userDetailsService(service);
}
// @Bean
// @Override
// public AuthenticationManager authenticationManagerBean() throws Exception {
// return super.authenticationManagerBean();
// }
@Override
protected void configure(HttpSecurity http) throws Exception {
// 禁用CSRF 开启跨域
http.csrf().disable().cors();
http
.authorizeRequests()
// .antMatchers("/product/**").hasRole("user")//部分请求需要某个角色才能访问,角色大小写要注意
// .antMatchers("/admin/**").hasRole("admin1")
// .antMatchers("/hello*").authenticated()
.withObjectPostProcessor(new ObjectPostProcessor<FilterSecurityInterceptor>() {
@Override
public <O extends FilterSecurityInterceptor> O postProcess(O o) {
o.setSecurityMetadataSource(filterMetadataSource);
o.setAccessDecisionManager(myAccessDecisionManager);
return o;
}
}) // 定义哪些URL需要被保护、哪些不需要被保护
.anyRequest().authenticated()// 任何请求,登录后可以访问
.and().httpBasic()//http支持基础认证
. and().formLogin()// 定义当需要用户登录时候,转到的登录页面。默认是spring-security自带的
// .successForwardUrl("/hello")//登录成功默认跳转的地址
;
}
@Override
public void configure(WebSecurity web) throws Exception {
web.ignoring().antMatchers("/vercode");//忽略某些地址
}
}
这样,基本就实现了动态url配置了