1. 权限管理过程中的相关概念
1.1 主体
英文单词:principal
使用系统的用户或设备或从其他系统远程登录的用户等等。简单说就是谁使用系统谁就是主体。
1.2 认证
英文单词:authentication
权限管理系统确认一个主体的身份,允许主体进入系统。简单说就是“主体”证明自己是谁。
笼统的认为就是以前所做的登录操作。
1.3 授权
英文单词:authorization
将操作系统的“权力”“授予”“主体”,这样主体就具备了操作系统中特定功能的能力。
所以简单来说,授权就是给用户分配权限。
2. 权限管理的主流框架
2.1 SpringSecurity
Spring技术栈的组成部分。
通过提供完整可扩展的认证和授权支持保护你的应用程序。
官方网址
SpringSecurity特点:
- 和Spring无缝整合。
- 全面的权限控制。
- 专门为Web开发而设计。
- 旧版本不能脱离Web环境使用。
- 新版本对整个框架进行了分层抽取,分成了核心模块和Web模块。单独引入核心模块就可以脱离Web环境。
- 重量级。
2.2 Shiro
Apache旗下的轻量级权限控制框架。
特点:
- 轻量级。Shiro主张的理念是把复杂的事情变简单。针对对性能有更高要求的互联网应用有更好表现。
- 通用性。
- 好处:不局限于Web环境,可以脱离Web环境使用。
- 缺陷:在Web环境下一些特定的需求需要手动编写代码定制。
官网网址
3. 使用配置类代替XML配置文件
3.1 @Configuration注解
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Component//由于当前注解带有@Component注解,所以标记当前注解的类可以享受包的自动扫描
public @interface Configuration {
/**
* Explicitly specify the name of the Spring bean definition associated
* with this Configuration class. If left unspecified (the common case),
* a bean name will be automatically generated.
*
* The custom name applies only if the Configuration class is picked up via
* component scanning or supplied directly to a {@link AnnotationConfigApplicationContext}.
* If the Configuration class is registered as a traditional XML bean definition,
* the name/id of the bean element will take precedence.
*
* @return the specified bean name, if any
* @see org.springframework.beans.factory.support.DefaultBeanNameGenerator
*/
String value() default "";
}
类标记了这个注解就可以使用这个类代替Spring的XML配置文件。
3.2 @Bean注解
用来代替XML配置文件中的bean标签。下面两种形式效果一致:
@Configuration
public class AnnotaionConfig{
@Bean
public EmpHandler getEmpHandler(){
return new EmpHandler();
}
}
提示:Spring通过调用标记了@Bean注解的方法将对象放入IOC容器行为不会重复调用方法。原因是Spring想要获取bean对应的实例对象时会查看IOC容器中是否已经有了这个对象,如果有则不会执行这个方法,从而保证这个bean是单一实例的。
如果希望对应的bean是多实例的,则可以配合@Scope注解。
4. HelloWorld工程创建步骤
4.1 创建Maven的Web工程
4.2 加入SpringMVC环境需要的依赖
org.springframework
spring-webmvc
4.3.20.RELEASE
javax.servlet
servlet-api
2.5
provided
javax.servlet.jsp
jsp-api
2.1.3-b06
provided
4.3 创建SpringMVC配置文件
4.4 在web.xml中配置DispatcherServlet
springDispatcherServlet
org.springframework.web.servlet.DispatcherServlet
contextConfigLocation
classpath:spring-mvc.xml
1
springDispatcherServlet
/
4.5 创建包
com.atguigu.security.controller
4.6 从例子工程中复制Controller
4.7 加入webapp目录下文件
5 在HelloWorld基础上加入SpringSecurity
5.1 加入SpringSecurity依赖
org.springframework.security
spring-security-web
4.2.10.RELEASE
org.springframework.security
spring-security-config
4.2.10.RELEASE
org.springframework.security
spring-security-taglibs
4.2.10.RELEASE
5.2 加入SpringSecurity控制权限的Filter
SpringSecurity使用的是过滤器Filter而不是拦截器Interceptor,意味着SpringSecurity能够管理的不仅仅是SpringMVC中的handler请求,还包含Web应用中所有请求。比如:项目中的静态资源也会被拦截,从而进行权限控制。
springSecurityFilterChain
org.springframework.web.filter.DelegatingFilterProxy
springSecurityFilterChain
/*
特别注意:
springSecurityFilterChain标签中必须是springSecurityFilterChain。因为springSecurityFilterChain在IOC容器中对应真正执行权限控制的二十几个Filter,只有叫这个名字才能够加载到这些Filter。
5.3 加入配置类
com.rgh.security.config.WebAppSecurityConfig
@Configuration
@EnableWebSecurity
public class WebAppSecurityConfig extends WebSecurityConfigurerAdapter {
}
Enable理解为启用。
@EnableWebSecurity注解表示启用Web安全功能。
以后会接触到很多@EnableXxx注解,用来启用对应的功能。
5.4 效果
- 所有请求都被SpringSecurity拦截,要求登录才可以访问。
- 静态资源也都被拦截,要求登录。
- 登录失败有错误提示。
6 SpringSecurity操作实验
下面的操作都是在HelloWorld的基础上逐步增加权限控制设置,循序渐进学习SpringSecurity用法.
6.1 实验1:放行首页和静态资源
重写父类的configure(HttpSecurity security)方法。
protected void configure(HttpSecurity security) throws Exception {
logger.debug("Using default configure(HttpSecurity). If subclassed this will potentially override subclass configure(HttpSecurity).");
security
.authorizeRequests()
.anyRequest().authenticated() //所有请求都需要进行认证
.and()
.formLogin()
.and()
.httpBasic();
}
重写后
@Override
protected void configure(HttpSecurity security) throws Exception {
//super.configure(security); 注释掉将取消父类方法中的默认规则
security.authorizeRequests() //对请求进行授权
.antMatchers("/layui/**","/index.jsp") //使用ANT风格设置要授权的URL地址
.permitAll() //允许上面使用ANT风格设置的全部请求
.anyRequest() //其他未设置的全部请求
.authenticated(); //需要认证
}
效果:未授权的请求会跳转到403错误页面。
6.2 实验2:未授权请求跳转到登录页
@Override
protected void configure(HttpSecurity security) throws Exception {
//super.configure(security); 注释掉将取消父类方法中的默认规则
security.authorizeRequests() //对请求进行授权
.antMatchers("/layui/**","/index.jsp") //使用ANT风格设置要授权的URL地址
.permitAll() //允许上面使用ANT风格设置的全部请求
.anyRequest() //其他未设置的全部请求
.authenticated() //需要认证
.and()
.formLogin() //设置未授权请求跳转到登录页面
.loginPage("/index.jsp") //指定登录页
.permitAll(); //为登录页设置所有人都可以访问
}
指定登录页前后SpringSecurity登录地址变化:
登陆指定前/后 | 地址栏 |
---|---|
指定前 | /login GET - the login form |
/login POST - process the credentials and if valid authenticate the user | |
/login?error GET - redirect here for failed authentication attempts | |
/login?logout GET - redirect here after successfully logging out | |
指定后 | /index.jsp GET - the login form |
/index.jsp POST - process the credentials and if valid authenticate the user | |
/index.jsp?error GET - redirect here for failed authentication attempts | |
/index.jsp?logout GET - redirect here after successfully logging out |
通过调用loginProcessingUrl()方法指定登录地址。
6.3 实验3:设置登录系统的账号、密码
6.3.1 页面设置
给index.jsp设置表单
${SPRING_SECURITY_LAST_EXCEPTION.message}
※提示:如果希望在指定登录页面地址后指定登录操作本身地址,可以调用loginProcessingUrl("登录地址").permitAll()方法。
账号、密码的请求参数名
- 默认账号的请求参数名:username
- 默认密码的请求参数名:password
要么修改页面上的表单项的name属性值,要么修改配置。如果修改配置可以调用usernameParameter()和passwordParameter()方法。
6.3.2 后端配置
设置登录成功后默认前往的页面
@Override
protected void configure(HttpSecurity security) throws Exception {
//super.configure(security); 注释掉将取消父类方法中的默认规则
security.authorizeRequests() //对请求进行授权
.antMatchers("/layui/**","/index.jsp") //使用ANT风格设置要授权的URL地址
.permitAll() //允许上面使用ANT风格设置的全部请求
.anyRequest() //其他未设置的全部请求
.authenticated() //需要认证
.and()
.formLogin() //设置未授权请求跳转到登录页面:开启表单登录功能
.loginPage("/index.jsp") //指定登录页
.permitAll() //为登录页设置所有人都可以访问
.defaultSuccessUrl("/main.html"); //设置登录成功后默认前往的URL地址
}
重新另外一个父类的方法,来设置登录系统的账号密码
@Override
protected void configure(AuthenticationManagerBuilder builder) throws Exception {
//super.configure(auth); 一定要禁用默认规则
builder.inMemoryAuthentication()
.withUser("tom").password("123123") //设置账号密码
.roles("ADMIN") //设置角色
.and()
.withUser("jerry").password("456456")//设置另一个账号密码
.authorities("SAVE","EDIT"); //设置权限
}
Cannot pass a null GrantedAuthority collection问题是由于没有设置roles()或authorities()方法导致的。
6.3.3 ※了解:_csrf如何防止跨站请求伪造?
Cross-site request forgery跨站请求伪造
发送登录请求时没有携带_csrf值,则返回下面错误:
从钓鱼网站的页面提交的请求无法携带正确、被承认的令牌。
面试相关问题:在单点登录系统中,认证中心根据浏览器的Cookie识别用户身份。那如果用户的Cookie被劫持仿冒用户身份登录系统怎么办?
除了Cookie之外,还使用_csrf生成的token防止跨站请求伪造。
最后:登录成功后具体资源都可以访问了。
6.4 实验4:用户注销
通过调用HttpSecurity对象的一系列方法设置注销功能。
logout()方法:开启注销功能
logoutUrl()方法:自定义注销功能的URL地址
如果CSRF功能没有禁用,那么退出请求必须是POST方式。如果禁用了CSRF功能则任何请求方式都可以。
logoutSuccessUrl()方法:退出成功后前往的URL地址
addLogoutHandler()方法:添加退出处理器
logoutSuccessHandler()方法:退出成功处理器
退出的表单
退出
所属类:WebAppSecurityConfig
@Override
protected void configure(HttpSecurity security) throws Exception {
security
.authorizeRequests() // 对请求进行授权
.antMatchers("/index.jsp","/layui/**") // 设置要进行授权的请求地址
.permitAll() // antMatchers()匹配的请求直接放行
.anyRequest() // 除了antMatchers()匹配的其他请求
.authenticated() // 需要“认证”后才可以访问
.and()
.formLogin() // 指定使用表单作为登录方式
.loginPage("/index.jsp") // 指定登录页地址
.permitAll()
.defaultSuccessUrl("/main.html") // 登录成功后访问的地址
.usernameParameter("loginacct") // 告诉SpringSecurity登录账号的请求参数名
.passwordParameter("credential") // 告诉SpringSecurity登录密码的请求参数名
.and()
.logout() // 开启用户“退出登录”功能
.logoutUrl("/my/app/logout") // 设置退出登录URL地址
.logoutSuccessUrl("/index.jsp"); // 成功退出登录后前往的地址
}
6.5 实验5:基于角色进行访问控制
所属类:WebAppSecurityConfig
通过HttpSecurity对象设置资源的角色要求
@Override
protected void configure(HttpSecurity security)throws Exception {
security.authorizeRequests() //对请求进行授权
.antMatchers("/layui/**","/index.jsp") //使用ANT风格设置要授权的URL地址
.permitAll() //允许上面使用ANT风格设置的全部请求
.antMatchers("/level1/**")
.hasRole("学徒")
.antMatchers("/level2/**")
.hasRole("大师")
.antMatchers("/level3/**")
.hasRole("宗师")
.anyRequest() //其他未设置的全部请求
.authenticated() //需要认证
.and()
.formLogin() //设置未授权请求跳转到登录页面:开启表单登录功能
.loginPage("/index.jsp") //指定登录页
.permitAll() //为登录页设置所有人都可以访问
.defaultSuccessUrl("/main.html") //设置登录成功后默认前往的URL地址
.and()
.logout()
.logoutUrl("/my/logout")
.logoutSuccessUrl("/index.jsp");
}
通过AuthenticationManagerBuilder对象设置用户登录时具备的角色
@Override
protected void configure(AuthenticationManagerBuilder builder) throws Exception {
builder.inMemoryAuthentication()
.withUser("tom").password("123123") //设置账号密码
.roles("ADMIN","学徒","宗师") //设置角色
.and()
.withUser("jerry").password("456456")//设置另一个账号密码
.authorities("SAVE","EDIT"); //设置权限
}
访问被拒绝后看到403错误页面:
注意:调用顺序
.antMatchers("/level1/**") //设置匹配/level1/**的地址
.hasRole("学徒") //要求具备“学徒”角色
.antMatchers("/level2/**")
.hasRole("大师")
.antMatchers("/level3/**")
.hasRole("宗师")
.anyRequest() //其实未设置的所有请求
.authenticated() //需要认证才可以访问
蓝色代码设置范围更大
红色代码设置范围相对小
如果蓝色代码先调用,会把后面红色代码的设置覆盖,导致红色代码无效。所以要先做具体小范围设置,再做大范围模糊设置。
6.6 实验6:自定义403错误页面
由main.jsp复制得到no_auth.jsp。修改如下:
抱歉!您没有权限访问此功能!
前往自定义页面方式一:
所在类:AdminController
@RequestMapping("/to/no/auth/page")
public String toNoAuthPage() {
return "no_auth";
}
所在类:WebAppSecurityConfig
方法:
@Override
protected void configure(HttpSecurity security)
HttpSecurity对象.exceptionHandling().accessDeniedPage("/to/no/auth/page");
.logoutSuccessUrl("/index.jsp") // 成功退出登录后前往的地址
.and()
.exceptionHandling()
.accessDeniedPage("/to/no/auth/page.html") // 访问被拒绝后前往的页面
;
前往自定义页面方式二:
HttpSecurity对象.exceptionHandling().accessDeniedHandler(new AccessDeniedHandler() {
@Override
public void handle(HttpServletRequest request, HttpServletResponse response,
AccessDeniedException accessDeniedException) throws IOException, ServletException {
request.setAttribute("message", accessDeniedException.getMessage());
request.getRequestDispatcher("/WEB-INF/views/no_auth.jsp").forward(request, response);
}
});
6.7 实验7:记住我-内存版
HttpSecurity对象调用rememberMe()方法。
登录表单携带名为remember-me的请求参数。具体做法是将登录表单中的checkbox的name设置为remember-me
如果不能使用“remember-me”作为请求参数名称,可以使用rememberMeParameter()方法定制。
原理简要分析:
通过开发者工具看到浏览器端存储了名为remember-me的Cookie。根据这个Cookie的value在服务器端找到以前登录的User。
而且这个Cookie被设置为存储2个星期的时间。
6.8 实验8:记住我-数据库版
为了让服务器重启也不影响记住登录状态,将用户登录状态信息存入数据库。
6.8.1 建立数据库连接
依赖
com.alibaba
druid
1.1.12
mysql
mysql-connector-java
5.1.47
org.springframework
spring-orm
4.3.20.RELEASE
spring-mvc.xml:配置数据源
创建数据库
CREATE DATABASE `security` CHARACTER SET utf8;
在WebAppSecurityConfig类中注入数据源
@Autowired
private DataSource dataSource;
6.8.2 启用令牌仓库功能
所在类:WebAppSecurityConfig
方法:
@Override
protected void configure(HttpSecurity security){
JdbcTokenRepositoryImpl repository = new JdbcTokenRepositoryImpl();
repository.setDataSource(dataSource);
security.
------
.accessDeniedPage("/to/no/auth/page.html") // 访问被拒绝后前往的页面
.and()
.rememberMe() // 开启记住我功能
.tokenRepository(repository) // 指定token库
;
}
HttpSecurity对象.tokenRepository(repository);
注意:需要进入JdbcTokenRepositoryImpl 类中找到创建persistent_logins表的SQL语句创建persistent_logins表。
CREATE TABLE persistent_logins (
username VARCHAR (64) NOT NULL,
series VARCHAR (64) PRIMARY KEY,
token VARCHAR (64) NOT NULL,
last_used TIMESTAMP NOT NULL
);
6.9 实验9:查询数据库完成认证
6.9.1 了解:SpringSecurity默认实现
builder.jdbcAuthentication().usersByUsernameQuery("tom");
在usersByUsernameQuery("tom")等方法中最终调用JdbcDaoImpl类的方法查询数据库。
SpringSecurity的默认实现已经将SQL语句硬编码在了JdbcDaoImpl类中。这种情况下,我们有下面三种选择:
- 按照JdbcDaoImpl类中SQL语句设计表结构。
- 修改JdbcDaoImpl类的源码。
- 不使用jdbcAuthentication()。
6.9.2 自定义数据库查询方式
builder.userDetailsService(userDetailsService)
其中userDetailsService需要自定义实现UserDetailsService接口的类并自动装配。
所在类:AppUserDetailService
@Service
public class AppUserDetailService implements UserDetailsService {
@Autowired
private JdbcTemplate jdbcTemplate;
@Override
public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
// 1.使用SQL语句根据用户名查询用户对象
String sql = "SELECT id,loginacct,userpswd,username,email,createtime FROM t_admin WHERE loginacct = ?";
// 2.获取查询结果
Map resultMap = jdbcTemplate.queryForMap(sql, username);
// 3.获取用户名、密码数据
String loginacct = resultMap.get("loginacct").toString();
String userpswd = resultMap.get("userpswd").toString();
// 4.创建权限列表
List list = AuthorityUtils.createAuthorityList("ROLE_学徒","ROLE_大师");
return new User(loginacct, userpswd, list);
}
}
create table t_admin
(
id int not null auto_increment,
loginacct varchar(255) not null,
userpswd char(32) not null,
username varchar(255) not null,
email varchar(255) not null,
createtime char(19),
primary key (id)
);
6.9.3 使用自定义UserDetailsService完成登录
所在类:WebAppSecurityConfig
方法:
@Autowired
private AppUserDetailService userDetailService;
protected void configure(AuthenticationManagerBuilder builder) throws Exception {
// builder
// .inMemoryAuthentication()
// .withUser("tom") // 指定登录系统的账号
// .password("123123") // 指定账号对应的密码
// .roles("大师") // 必须设置角色或权限,否则会出现Cannot pass a null GrantedAuthority collection错误
// ;
builder.userDetailsService(userDetailService);
}
6.9.4 “ROLE_”前缀问题
在自定义的UserDetailsService中,使用org.springframework.security.core.authority.AuthorityUtils.createAuthorityList(String...)工具方法获取创建SimpleGrantedAuthority对象添加角色时需要手动在角色名称前加“ROLE_”前缀。
6.10 实验10:应用自定义密码加密规则
自定义类实现org.springframework.security.crypto.password.PasswordEncoder(使用没有过时的)接口。
所在类:PasswordEncoderService
@Service
public class PasswordEncoderService implements PasswordEncoder {
// 对原始明文密码进行加密
@Override
public String encode(CharSequence rawPassword) {
Assert.notNull(rawPassword, "rawPassword can not be null!");
String password = CrowdFundingUtils.md5(rawPassword.toString());
return password;
}
// 将明文密码和密文密码进行比较
@Override
public boolean matches(CharSequence rawPassword, String encodedPassword) {
Assert.notNull(rawPassword, "rawPassword can not be null!");
String password = CrowdFundingUtils.md5(rawPassword.toString());
return Objects.equals(password, encodedPassword);
}
}
- encode()方法对明文进行加密。
- matches()方法对明文加密后和密文进行比较。
在配置类中的configure(AuthenticationManagerBuilder)方法中应用自定义密码加密规则
所在类:WebAppSecurityConfig
@Autowired
private PasswordEncoder passwordEncoder;
@Override
protected void configure(AuthenticationManagerBuilder builder) throws Exception {
builder.userDetailsService(userDetailService).passwordEncoder(passwordEncoder);
}
※SpringSecurity提供的BCryptPasswordEncoder加密规则。
BCryptPasswordEncoder创建对象后代替自定义passwordEncoder对象即可。BCryptPasswordEncoder在加密时通过加入随机盐值让每一次的加密结果都不同。能够避免密码的明文被猜到。
而在对明文和密文进行比较时,BCryptPasswordEncoder会在密文的固定位置取出盐值,重新进行加密。
所在类:BCryptPasswordEncoderTest
代码:
public class BCryptPasswordEncoderTest {
public static void main(String[] args) {
BCryptPasswordEncoder encoder = new BCryptPasswordEncoder();
CharSequence rawPassword = "123123";
for(int i = 0; i < 10; i++) {
String encodedPassword = encoder.encode(rawPassword);
System.out.println(encodedPassword);
}
System.out.println();
boolean matches = encoder.matches(rawPassword, "$2a$10$Y2Cq8ilT21ME.lvu6bwcPO/RMkU7ucAZpmFzx7GDTXK9KNxHyEM1e");
System.out.println(matches);
}
}
7 众筹项目加入SpringSecurity环境
7.1 加入依赖
org.springframework.security
spring-security-web
4.2.10.RELEASE
org.springframework.security
spring-security-config
4.2.10.RELEASE
org.springframework.security
spring-security-taglibs
4.2.10.RELEASE
7.2 Filter
web.xml
springSecurityFilterChain
org.springframework.web.filter.DelegatingFilterProxy
springSecurityFilterChain
/*
7.3 配置类
CrowdfundingSecurityConfig
@Configuration
@EnableWebSecurity
@EnableGlobalMethodSecurity(prePostEnabled=true)
public class CrowdfundingSecurityConfig extends WebSecurityConfigurerAdapter {
}
@EnableGlobalMethodSecurity(prePostEnabled=true)注解表示启用全局方法权限管理功能。
7.4 自动扫描的包
考虑到权限控制系统更多的需要控制Web请求,而且有些请求没有经过Service方法,所以在SpringMVC的IOC容器中扫描CrowdfundingSecurityConfig。但是,SpringSecurity是有管理Service、Dao方法的能力的。
配置文件:/atcrowdfunding-admin-1-webui/src/main/resources/spring-web-mvc.xml
7.5 多个IOC容器之间的关系
问题描述:项目启动时控制台抛异常说找不到“springSecurityFilterChain”的bean。
org.springframework.beans.factory.NoSuchBeanDefinitionException: No bean named 'springSecurityFilterChain' is defined
问题分析:
Web组件加载顺序:Listener→Filter→Servlet
- Spring IOC容器:ContextLoaderListener创建
- SpringMVC IOC容器:DispatcherServlet创建
- springSecurityFilterChain:从IOC容器中找到对应的bean
ContextLoaderListener初始化后,springSecurityFilterChain就在ContextLoaderListener创建的IOC容器中查找所需要的bean,但是我们没有在ContextLoaderListener的IOC容器中扫描SpringSecurity的配置类,所以springSecurityFilterChain对应的bean找不到。
问题解决:
将ContextLoaderListener取消,原本由ContextLoaderListener读取的Spring配置文件交给DispatcherServlet负责读取。
springDispatcherServlet
org.springframework.web.servlet.DispatcherServlet
contextConfigLocation
classpath:spring-web-mvc.xml,classpath:spring-persist-*.xml
1
springDispatcherServlet
*.html
*.json
7.6 SpringSecurity初始设置
放行首页、静态资源。
@Override
protected void configure(HttpSecurity security) throws Exception {
security.authorizeRequests()
.antMatchers("/index.html","/bootstrap/**","/css/**","/fonts/**","/img/**","/jquery/**","/layer/**","/script/**","/ztree/**")
.permitAll()
.anyRequest()
.authenticated();
}
8 登录
8.1 SpringSecurity开启表单登录功能并前往登录表单页面
.formLogin()
.loginPage("/admin/to/login/page.html")
.permitAll()
8.2 循环重定向问题
去登录页面和登录请求本身都需要permitAll()否则登录和去登录页面本身都需要登录,形成死循环。
8.3 提交登录表单
注意:我们以前自己写的登录handler方法以后就不使用了。使用SpringSecurity之后,登录请求由SpringSecurity处理。
security
.authorizeRequests()
.antMatchers("/index.html")
.permitAll()
.antMatchers("/bootstrap/**")
.permitAll()
.antMatchers("/css/**")
.permitAll()
.antMatchers("/fonts/**")
.permitAll()
.antMatchers("/img/**")
.permitAll()
.antMatchers("/jquery/**")
.permitAll()
.antMatchers("/layer/**")
.permitAll()
.antMatchers("/script/**")
.permitAll()
.antMatchers("/ztree/**")
.permitAll()
.anyRequest()
.authenticated()
.and()
.formLogin()
.loginPage("/admin/to/login/page.html")
.permitAll()
.loginProcessingUrl("/admin/security/login.html")
.permitAll()
.usernameParameter("loginacct")
.passwordParameter("userpswd")
.defaultSuccessUrl("/admin/to/main/page.html")
.and()
.logout()
.logoutUrl("/admin/security/logout.html")
.logoutSuccessUrl("/index.html")
.and()
.csrf()
.disable(); // 禁用CSRF功能
//禁用CSRF功能。注意:这仅仅是我们学习过程中偷懒的做法,实际开发时还是不要禁用。
security.csrf().disable();
8.4 登录操作查询相关数据的SQL
所在类:CrowdFundingUserDetailsService
// 1.根据用户名从数据库查询Admin对象
AdminExample adminExample = new AdminExample();
adminExample
.createCriteria()
.andLoginacctEqualTo(username);
List adminList = adminMapper.selectByExample(adminExample);
List roleList = roleMapper.selectAssignRoleList(adminId);
8.5 SecurityAdmin封装
/**
* 扩展User类
* 创建SecurityAdmin对象时调用构造器,传入originalAdmin和authorities
* 可以通过getOriginalAdmin()方法获取原始Admin对象
*
*/
public class SecurityAdmin extends User {
private static final long serialVersionUID = 1L;
private Admin originalAdmin;
public SecurityAdmin(Admin originalAdmin, Collection extends GrantedAuthority> authorities) {
super(originalAdmin.getLoginAcct(), originalAdmin.getUserPswd(), authorities);
this.originalAdmin = originalAdmin;
}
public Admin getOriginalAdmin() {
return originalAdmin;
}
}
8.6 loadUserByUsername(String username)方法
所在类:CrowdFundingUserDetailsService
@Service
public class CrowdFundingUserDetailsService implements UserDetailsService {
@Autowired
private AdminMapper adminMapper;
@Autowired
private RoleMapper roleMapper;
@Autowired
private AuthMapper authMapper;
@Override
public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
// 1.根据用户名从数据库查询Admin对象
AdminExample adminExample = new AdminExample();
adminExample
.createCriteria()
.andLoginacctEqualTo(username);
List adminList = adminMapper.selectByExample(adminExample);
if(adminList == null || adminList.size() != 1) {
return null;
}
Admin admin = adminList.get(0);
// 2.获取数据库中密码
// String userpswd = admin.getUserpswd();
// 3.查询Admin对应的权限信息(包括角色、权限)
Integer adminId = admin.getId();
// ①创建集合用来存放权限信息
Collection authorities = new ArrayList<>();
// ②根据adminId查询对应的角色
List roleList = roleMapper.selectAssignRoleList(adminId);
for (Role role : roleList) {
String roleName = role.getName();
// 注意:一定要加“ROLE_”
authorities.add(new SimpleGrantedAuthority("ROLE_"+roleName));
}
// ③根据adminId查询对应的权限
List authNameList = authMapper.selectAssignedAuthList(adminId);
for (String authName : authNameList) {
authorities.add(new SimpleGrantedAuthority(authName));
}
// 4.封装到User的子类SecurityAdmin类型的对象中
// User user = new User(username, userpswd, authorities );
SecurityAdmin securityAdmin = new SecurityAdmin(admin, authorities);
return securityAdmin;
}
}
9 认证功能问题调整
9.1 取消手动进行登录检查的拦截器
Spring MVC配置文件
/atcrowdfunding-1-ui/src/main/resources/spring-web.xml
9.2 登录成功后显示实际登录的用户名
所在文件:include-nav.jsp
第一步:导入SpringSecurity标签库
<%@ taglib uri="http://www.springframework.org/security/tags" prefix="security" %>
第二步:使用security:authentication标签
9.3 加入关联关系假数据
页面操作或者直接将数据插入到数据库中即可。
9.4 保存Admin时使用SpringSecurity加密方式
所在类:AdminServiceImpl
@Autowired
private PasswordEncoder passwordEncoder;
@Override
public void saveAdmin(Admin admin) {
//对密码进行加密
String userPswd = admin.getUserPswd();
userPswd = passwordEncoder.encode(userPswd);
admin.setUserPswd(userPswd);
//执行保存
adminMapper.insert(admin);
}
10 权限控制
10.1 handler方法的权限控制
- linda:
- ROLE_总裁
- role:get
- peiqi:
- ROLE_经理
- user:get
需要进行权限控制的handler方法
全类名:com.atguigu.crowd.handler.AdminHandler
@PreAuthorize(value="hasRole('PM - 项目经理')")
@RequestMapping("/admin/query")
public String queryWithSearch(
@RequestParam(value="keyword", defaultValue="") String keyword,
@RequestParam(value="pageNo", defaultValue="1") int pageNo,
Model model
) {
// 1.调用Service方法获取分页数据
PageInfo pageInfo = adminService.getAdminPageInfoWithKeyword(keyword, pageNo, ArgumentsConstant.PAGE_SIZE);
// 2.将分页数据存入模型
model.addAttribute(AttrNameConstant.PAGE, pageInfo);
// 3.跳转页面
return "admin_page";
}
注意:@PreAuthorize注解生效需要@EnableGlobalMethodSecurity(prePostEnabled=true)注解支持。
10.2 使用全局配置控制
所在类:CrowdfundingSecurityConfig
.antMatchers("/admin/query/for/search.html")
.hasRole("董事长")
……
.and()
.exceptionHandling()
.accessDeniedHandler(new CrowdFundingAccessDeniedHandler())
accessDeniedHandler()方法指定了检测到权限不匹配时的处理方式。
新建类实现AccessDeniedHandler接口
public class CrowdFundingAccessDeniedHandler implements AccessDeniedHandler {
@Override
public void handle(HttpServletRequest request, HttpServletResponse response, AccessDeniedException e) throws IOException, ServletException {
request.setAttribute("exception",e);
request.getRequestDispatcher("/WEB-INF/system-error.jsp").forward(request,response);
}
}
10.3 页面元素权限控制
页面:admin-main.jsp
使用SpringSecurity提供的标签可以详细对页面元素进行权限控制。
第一步:导入标签库
<%@ taglib uri="http://www.springframework.org/security/tags" prefix="security" %>
第二步:使用security:authorize标签