springboot-6-springSecurity
一、Spring Security的基本配置
安全需要在设计网站之初就需要做好设计
可以做到:
- 功能权限
- 访问权限
- 菜单权限
这些权限虽然用拦截器过滤器也能实现,但是很麻烦,所以我们一般使用框架实现
几个重要类:
- WebSecurityConfigureAdapter:自定义Security策略
- AuthenticationManagerBuilder:自定义认证策略
- @EnableWebSecurity:开启WebEnableSecurity模式
Spring Security的两个主要目标是“认证”和“授权”(即访问控制)
-
“认证”Authentication
-
“授权”Authorization
这个概念是通用的,而不只是在Spring Security中存在
流程:
导入依赖,是spring系列的框架
<!--security-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-security</artifactId>
</dependency>
写控制器
@RestController
public class Hello {
@GetMapping("/hello")
public String getHello(){
return "hello security";
}
}
访问:http://localhost:8080/hello
用户名是user
密码为:
登录进去之后才进入了hello的放回界面
配置文件指定用户名和密码:
application.properties
spring.security.user.name=admin
spring.security.user.password=123456
spring.security.user.roles=admin
配置类实现更多的配置:
这是一种基于内存的配置,通过继承WebSecurityConfigurerAdapter
要与数据库认证区分开来,配置角色时不需要再添加“ROLE_”前缀
1、指定用户名和密码和职责(认证)
1、添加配置类
2、继承WebSecurityConfigurerAdapter
3、重写configure(AuthenticationManagerBuilder auth)方法
4、添加加密方式
@Configuration
public class MyWebSecurityConfig extends WebSecurityConfigurerAdapter {
//springboot要求必须使用一种加密方式
//此处使用NoOpPasswordEncoder,即无密码加密方式
@Bean
PasswordEncoder passwordEncoder(){
return NoOpPasswordEncoder.getInstance();
}
@Override
protected void configure(AuthenticationManagerBuilder auth) throws Exception {
//配置两个用户
auth.inMemoryAuthentication()
.withUser("admin").password("123456").roles("ADMIN","USER")
.and()
.withUser("wang").password("123").roles("USER");
}
}
2、配置受保护资源(授权)
重写的是configure(HttpSecurity http)方法
@Override
protected void configure(AuthenticationManagerBuilder auth) throws Exception {
//添加了三个成员,分别是数据库管理员、管理员、和用户
auth.inMemoryAuthentication()
.withUser("root").password("123456").roles("ADMIN","DBA")
.and()
.withUser("admin").password("123456").roles("ADMIN")
.and()
.withUser("wang").password("123456").roles("USER");
}
@Override
protected void configure(HttpSecurity http) throws Exception {
http.authorizeRequests()
//管理员--->管理员角色
.antMatchers("/admin/**").hasRole("ADMIN")
//用户--->用户角色或者管理员角色有其一即可
.antMatchers("/user/**").access("hasAnyRole('ADMIN','USER')")
//数据库--->管理员角色和数据库管理员角色都要有才能访问
.antMatchers("/db/**").access("hasRole('ADMIN') and hasRole('DBA')")
//除去上面的请求模式需要特定的角色之外
// 其他请求都需要先经过认证,才能访问
.anyRequest().authenticated()
.and()
/*表单登录
* 登录url为"/login",注意这个“/”
* permitall表示所有与登录相关的接口都不需要认证就可以访问
* */
.formLogin().loginProcessingUrl("/login").permitAll()
.and()
//表示关闭csrf
.csrf().disable();
}
添加控制器--测试
@RestController
public class Hello {
//管理员
@GetMapping("/admin/hello")
public String admin(){
return "hello admin";
}
//用户
@GetMapping("/user/hello")
public String user(){
return "hello user";
}
//数据库管理员
@GetMapping("/db/hello")
public String db(){
return "hello dba";
}
//普通的hello
@GetMapping("/hello")
public String Hello(){
return "hello";
}
}
会发现只要登录了就可以访问/hello,还有这个用户对应的hello页面,其他以此类推
登录表单详细配置:
就是跟改上面授权中的.formLogin().loginProcessingUrl("/login").permitAll()
这一段配置
//表单提交数据
.formLogin()
//下面这个是登录界面,此处没有写这个界面,所以用postman测试
.loginPage("/login_page")
//下面三行值得是提交信息给哪个url,而且用的是哪个参数名称
.loginProcessingUrl("/login")
.usernameParameter("name")
.passwordParameter("password")
//在这个成功操作的方法里面放一个实体静态类
.successHandler(new AuthenticationSuccessHandler() {
@Override
public void onAuthenticationSuccess(HttpServletRequest request,
HttpServletResponse response,
Authentication authentication) throws IOException, ServletException {
//从认证成功那里得到关键信息
Object principal = authentication.getPrincipal();
PrintWriter out = response.getWriter();
response.setStatus(200);
//这个map就是我们要放回的json信息
HashMap<String, Object> map = new HashMap<>();
map.put("status",200);
map.put("msg",principal);
ObjectMapper om = new ObjectMapper();
out.write(om.writeValueAsString(map));
out.flush();
out.close();
}
})
.failureHandler(new AuthenticationFailureHandler() {
@Override
public void onAuthenticationFailure(HttpServletRequest request,
HttpServletResponse response,
AuthenticationException exception) throws IOException, ServletException {
response.setContentType("application/json;charset=utf-8");
PrintWriter out = response.getWriter();
response.setStatus(401);
HashMap<String, Object> map = new HashMap<>();
map.put("status",401);
if (exception instanceof LockedException){
map.put("msg","账户被锁,登陆失败");
}else if(exception instanceof BadCredentialsException){
map.put("msg","账户名或密码输入错误,登录失败");
}else if(exception instanceof DisabledException){
map.put("msg","账户被禁用,登陆失败");
}else if(exception instanceof CredentialsExpiredException){
map.put("msg","密码已过期,登陆失败");
}else if(exception instanceof AccountExpiredException){
map.put("msg","账户已过期,登陆失败");
}else{
map.put("msg","登陆失败");
}
ObjectMapper om = new ObjectMapper();
out.write(om.writeValueAsString(map));
out.flush();
out.close();
}
})
.permitAll()
此处主要是返回一段json数据,数据提交给/login接口
所以可以使用postman测试
注销登录操作:
注销登录,就是类似于qq下线一样
在后面加上一个新的板块
.and()
//开启注销登录的配置
.logout()
//配置注销登录提交星系交给哪个接口
.logoutUrl("/logout")
.clearAuthentication(true)
.invalidateHttpSession(true)
.addLogoutHandler(new LogoutHandler() {
@Override
public void logout(HttpServletRequest request,
HttpServletResponse response,
Authentication authentication) {
//这个地方根据需要完成一些数据清除工作,如cookie
}
})
.logoutSuccessHandler(new LogoutSuccessHandler() {
@Override
public void onLogoutSuccess(HttpServletRequest request, HttpServletResponse response, Authentication authentication) throws IOException, ServletException {
response.sendRedirect("/login_page");
}
})
多个Http Security同时配置:
略(见书)
二、密码加密
三、基于数据库认证
流程:
先创建好表
user、role、user_role三个表
填充数据
需要再整合mybatis
导入依赖
<!--mybatis-->
<dependency>
<groupId>org.mybatis.spring.boot</groupId>
<artifactId>mybatis-spring-boot-starter</artifactId>
<version>2.1.3</version>
</dependency>
<!--mysql-->
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
</dependency>
<!--druid-->
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>druid</artifactId>
<version>1.1.21</version>
</dependency>
配置数据库
spring.datasource.type=com.alibaba.druid.pool.DruidDataSource
spring.datasource.username=root
spring.datasource.password=123456
spring.datasource.url=jdbc:mysql://localhost:3306/springboot
创建对应pojo类,Role类和User类
@Data
public class Role {
private Integer id;
private String name;
private String nameZh;
}
public class User implements UserDetails {
private Integer id;
private String username;
private String password;
private boolean enabled;
private boolean locked;
private List<Role> roles;
public User() {
}
public Integer getId() {
return id;
}
public void setId(Integer id) {
this.id = id;
}
public void setUsername(String username) {
this.username = username;
}
public void setPassword(String password) {
this.password = password;
}
public void setEnabled(boolean enabled) {
this.enabled = enabled;
}
public boolean isLocked() {
return locked;
}
public void setLocked(boolean locked) {
this.locked = locked;
}
public List<Role> getRoles() {
return roles;
}
public void setRoles(List<Role> roles) {
this.roles = roles;
}
/*======================下面为重写方法======================*/
//这个方法用于获取当前用户所具有的角色对象
@Override
public Collection<? extends GrantedAuthority> getAuthorities() {
ArrayList<SimpleGrantedAuthority> authorities = new ArrayList<>();
for (Role role:roles){
authorities.add(new SimpleGrantedAuthority(role.getName()));
}
return authorities;
}
@Override
public String getUsername() {
return username;
}
@Override
public String getPassword() {
return password;
}
//因为数据库中没有创建过期未过期的字段,故直接返回true
@Override
public boolean isAccountNonExpired() {
return true;
}
@Override
public boolean isAccountNonLocked() {
return !locked;
}
@Override
public boolean isCredentialsNonExpired() {
return true;
}
@Override
public boolean isEnabled() {
return enabled;
}
}
创建UserMapper接口和UserMapper.xml
@Mapper
@Repository
public interface UserMapper {
//通过用户名去数据库找这个用户,如果没找到就返回一个账户不存在的错误
//如果找到了用户,就继续查找该用户对应的角色信息,并将获取到的用户信息返回,系统会自动去比对密码
User loadUseByUsername(String username);
//通过用户id来找到这个其对应的角色
List<Role> getUserRolesByUid(Integer id);
}
<?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.wang.dao.UserMapper">
<!--User loadUseByUsername(String username);-->
<select id="loadUseByUsername" parameterType="string" resultType="user">
select * from user where username = # {username}
</select>
<!--List<Role> getUserRolesByUid(Integer id);-->
<select id="getUserRolesByUid" parameterType="integer" resultType="role">
select * from role r , user_role ur where r.id = ur.rid and ur.uid = #{id}
</select>
</mapper>
创建UserService
@Service
public class UserService implements UserDetailsService {
@Autowired
UserMapper userMapper;
//参数即为表单输入的参数
@Override
public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
User user = userMapper.loadUseByUsername(username);
if (user == null){
throw new UsernameNotFoundException("账户不存在");
}
user.setRoles(userMapper.getUserRolesByUid(user.getId()));
return user;
}
}
配置Spring Security
User类怎么写
User类必须要实现UserDetails,里面有七个方法需要重写,
方法 | 解释 |
---|---|
getAuthorities() | 获取当前用户对象所具有的角色信息 |
getUsername() | 获取当前用户名 |
isCredentialsNonExpired() | 当前用户是否过期(字段) |
isAccountNonLocked() | 当前用户是否锁定(字段) |
isEnabled() | 当前账户是否可用(字段) |
getPassword() | 获取当前用户密码 |
isCredentialsNonExpired() | 当前密码是否过期(密码字段) |
getAuthorities()如何获取到角色信息的:
在User类中用户所具有的角色储存在了roles属性中,所以先创建一个SimpleGrantedAuthority的集合,
再遍历每个role,用他们的name创建一个个SimpleGrantedAuthority实体类,放到集合中去,最后放回这个集合即可。
UserService类怎么写的
必须要实现UserDetailsService类,并重写里面的loadUserByUsername
方法,这个方法可以直接获取到输入的username,把这个username通过usermapper去查找
如果没找到,我们需要自定自己的输出错误
如果找到了,系统会自动会匹配他的密码,根据相关数据,返回不同的错误
在然后我们通过这个user的id通过usermapper的另一个方法找到他的角色,再把这个角色又赋值给这个user,再返回这个user实例,实现了通过username加载user
一个完整的基于内存的配置
package com.wang.config;
import com.fasterxml.jackson.databind.ObjectMapper;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.authentication.*;
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.configuration.EnableWebSecurity;
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.AuthenticationException;
import org.springframework.security.crypto.password.NoOpPasswordEncoder;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.security.web.authentication.AuthenticationFailureHandler;
import org.springframework.security.web.authentication.AuthenticationSuccessHandler;
import org.springframework.security.web.authentication.logout.LogoutHandler;
import org.springframework.security.web.authentication.logout.LogoutSuccessHandler;
import javax.security.auth.login.CredentialException;
import javax.security.auth.login.CredentialExpiredException;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.io.PrintWriter;
import java.util.HashMap;
//@Configuration
public class MyWebSecurityConfig extends WebSecurityConfigurerAdapter {
//把这个实例放到IOC容器中去是为了使密码无加密
@Bean
PasswordEncoder passwordEncoder(){
return NoOpPasswordEncoder.getInstance();
}
@Override
protected void configure(AuthenticationManagerBuilder auth) throws Exception {
//添加了三个成员,分别是数据库管理员、管理员、和用户
auth.inMemoryAuthentication()
.withUser("root").password("123456").roles("ADMIN","DBA")
.and()
.withUser("admin").password("123456").roles("ADMIN")
.and()
.withUser("wang").password("123456").roles("USER");
}
@Override
protected void configure(HttpSecurity http) throws Exception {
http.authorizeRequests()
//管理员--->管理员角色
.antMatchers("/admin/**").hasRole("ADMIN")
//用户--->用户角色或者管理员角色有其一即可
.antMatchers("/user/**").access("hasAnyRole('ADMIN','USER')")
//数据库--->管理员角色和数据库管理员角色都要有才能访问
.antMatchers("/db/**").access("hasRole('ADMIN') and hasRole('DBA')")
//除去上面的请求模式需要特定的角色之外
// 其他请求都需要先经过认证,才能访问
.anyRequest().authenticated()
.and()
//表单提交数据
.formLogin()
//下面这个是登录界面,此处没有写这个界面,所以用postman测试
.loginPage("/login_page")
//下面三行值得是提交信息给哪个url,而且用的是哪个参数名称
.loginProcessingUrl("/login")
.usernameParameter("name")
.passwordParameter("password")
//在这个成功操作的方法里面放一个实体静态类
.successHandler(new AuthenticationSuccessHandler() {
@Override
public void onAuthenticationSuccess(HttpServletRequest request,
HttpServletResponse response,
Authentication authentication) throws IOException, ServletException {
//从认证成功那里得到关键信息
Object principal = authentication.getPrincipal();
PrintWriter out = response.getWriter();
response.setStatus(200);
//这个map就是我们要放回的json信息
HashMap<String, Object> map = new HashMap<>();
map.put("status",200);
map.put("msg",principal);
ObjectMapper om = new ObjectMapper();
out.write(om.writeValueAsString(map));
out.flush();
out.close();
}
})
.failureHandler(new AuthenticationFailureHandler() {
@Override
public void onAuthenticationFailure(HttpServletRequest request,
HttpServletResponse response,
AuthenticationException exception) throws IOException, ServletException {
response.setContentType("application/json;charset=utf-8");
PrintWriter out = response.getWriter();
response.setStatus(401);
HashMap<String, Object> map = new HashMap<>();
map.put("status",401);
if (exception instanceof LockedException){
map.put("msg","账户被锁,登陆失败");
}else if(exception instanceof BadCredentialsException){
map.put("msg","账户名或密码输入错误,登录失败");
}else if(exception instanceof DisabledException){
map.put("msg","账户被禁用,登陆失败");
}else if(exception instanceof CredentialsExpiredException){
map.put("msg","密码已过期,登陆失败");
}else if(exception instanceof AccountExpiredException){
map.put("msg","账户已过期,登陆失败");
}else{
map.put("msg","登陆失败");
}
ObjectMapper om = new ObjectMapper();
out.write(om.writeValueAsString(map));
out.flush();
out.close();
}
})
.permitAll()
.and()
//开启注销登录的配置
.logout()
//配置注销登录提交星系交给哪个接口
.logoutUrl("/logout")
.clearAuthentication(true)
.invalidateHttpSession(true)
.addLogoutHandler(new LogoutHandler() {
@Override
public void logout(HttpServletRequest request,
HttpServletResponse response,
Authentication authentication) {
//这个地方根据需要完成一些数据清除工作,如cookie
}
})
.logoutSuccessHandler(new LogoutSuccessHandler() {
@Override
public void onLogoutSuccess(HttpServletRequest request, HttpServletResponse response, Authentication authentication) throws IOException, ServletException {
response.sendRedirect("/login_page");
}
})
.and()
.csrf()
.disable();
}
}
一个完整的基于数据库的配置
@Configuration
public class MyConfig extends WebSecurityConfigurerAdapter {
@Autowired
UserService userService;
@Bean
PasswordEncoder passwordEncoder(){
return new BCryptPasswordEncoder();
}
//配置角色的方法
@Override
protected void configure(AuthenticationManagerBuilder auth) throws Exception {
auth.userDetailsService(userService);
}
@Override
protected void configure(HttpSecurity http) throws Exception {
http.authorizeRequests()
.antMatchers("/admin/**").hasRole("admin")
.antMatchers("/db/**").hasRole("dba")
.antMatchers("/user/**").hasRole("user")
.anyRequest().authenticated()
.and()
.formLogin().loginProcessingUrl("/login").permitAll()
.and()
.csrf().disable();
}
}
角色继承
1、基于security框架的角色继承
在配置类中添加一个bean到IOC容器中去
这个配置保证了dba的权限包含了admin的,admin又包含了dba的
@Bean
RoleHierarchy roleHierarchy(){
RoleHierarchyImpl roleHierarchy = new RoleHierarchyImpl();
String hierarchy = "ROLE_dba > ROLE_admin > ROLE_user";
roleHierarchy.setHierarchy(hierarchy);
return roleHierarchy;
}
2、动态配置权限
使用httpSecurity配置的认证授权规则还是不够灵活,无法实现资源和角色的动态调整,需要开发者自定义权限配置
(1)、数据库设计
在原来的数据库的基础上再增加资源表和资源角色表
(2)添加配置组件类
略
四、整合shiro
流程:
添加依赖
<!--shiro-->
<dependency>
<groupId>org.apache.shiro</groupId>
<artifactId>shiro-spring-boot-web-starter</artifactId>
<version>1.4.0</version>
</dependency>
<!--整合thymeleaf技术-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-thymeleaf</artifactId>
<!--不需要版本号-->
</dependency>
<dependency>
<groupId>com.github.theborakompanioni</groupId>
<artifactId>thymeleaf-extras-shiro</artifactId>
<version>2.0.0</version>
</dependency>
配置基本信息
# shiro配置
# 开启shiro配置
shiro.enabled=true
# 开启shiroweb配置
shiro.web.enabled=true
# 表示登录地址,默认为/login.jsp
shiro.loginUrl=/login
# 登陆成功地址,默认为/
shiro.successUrl=/index
# 表示当登录之后到未授权界面默认跳转地址
shiro.unauthorizedUrl=/unauthorized
# 表示是否允许通过url参数实现会话跟踪,默认为true(如果支持cookie的话一般设为false)
shiro.sessionManager.sessionIdUrlRewritingEnabled=true
# 表示是否允许通过cookie实现会话跟踪,默认为true
shiro.sessionManager.sessionIdCookieEnabled=true
配置shiro
package com.wang.config;
import at.pollux.thymeleaf.shiro.dialect.ShiroDialect;
import org.apache.shiro.realm.Realm;
import org.apache.shiro.realm.text.TextConfigurationRealm;
import org.apache.shiro.spring.web.config.DefaultShiroFilterChainDefinition;
import org.apache.shiro.spring.web.config.ShiroFilterChainDefinition;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
@Configuration
public class ShiroConfig {
//注意这个地方的realm对应的包时shiro下的包
@Bean
public Realm realm(){
TextConfigurationRealm realm = new TextConfigurationRealm();
realm.setUserDefinitions("sang=123,user\n admin=123,admin");
realm.setRoleDefinitions("admin=read,write\n user=read");
return realm;
}
@Bean
public ShiroFilterChainDefinition shiroFilterFactoryBean(){
DefaultShiroFilterChainDefinition chainDefinition = new DefaultShiroFilterChainDefinition();
//anon表示匿名,logout表示注销操作,authc表示需要登录操作
chainDefinition.addPathDefinition("/login","anon");
chainDefinition.addPathDefinition("/dologin","anon");
chainDefinition.addPathDefinition("/logout","logout");
chainDefinition.addPathDefinition("/**","authc");
return chainDefinition;
}
//如果在thymeleaf中不使用shiro标签的话,不需要这个bean
@Bean
public ShiroDialect shiroDialect(){
return new ShiroDialect();
}
}
对应的control
package com.wang.control;
import org.apache.shiro.SecurityUtils;
import org.apache.shiro.authc.AuthenticationException;
import org.apache.shiro.authc.UsernamePasswordToken;
import org.apache.shiro.authz.annotation.Logical;
import org.apache.shiro.authz.annotation.RequiresRoles;
import org.apache.shiro.subject.Subject;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PostMapping;
@Controller
public class LoginControl {
//dologin为登录接口
@PostMapping("doLogin")
public String doLogin(String username, String password, Model model){
UsernamePasswordToken token = new UsernamePasswordToken(username,password);
Subject subject = SecurityUtils.getSubject();
/* 尝试登录
* 登录失败:带一个错误信息去登录界面
* 登录成功:重定向到/index
* */
try{
subject.login(token);
}catch (AuthenticationException e){
model.addAttribute("error","用户名或密码错误");
return "login";
}
//这个重定向其实无所谓,因为在配置文件中已经配置了
return "redirect:/index";
}
@RequiresRoles("admin")
@GetMapping("/admin")
public String getAdmin(){
return "admin";
}
@RequiresRoles(value = {"admin","user"},logical = Logical.OR)
@GetMapping("/user")
public String getUser(){
return "user";
}
}
配置对应的mvc配置
@Configuration
public class WebMvcConfig implements WebMvcConfigurer {
@Override
public void addViewControllers(ViewControllerRegistry registry) {
//这些是不需要角色就可以访问到的接口
registry.addViewController("/login").setViewName("login");
registry.addViewController("/index").setViewName("index");
registry.addViewController("/unauthorized").setViewName("unauthorized");
}
}
还有五个界面, login.html , index.html , admin.html , user.html , unauthorized.html
<!DOCTYPE html>
<html lang="en" xmlns:th=http://www.thymeleaf.org
xmlns:sec=http://www.thymeleaf.org/extras/spring-security
xmlns:shiro=http://www.pollix.at/thymeleaf/shiro>
<head>
<title>Title</title>
</head>
<body>
<form th:action="@{/doLogin}" method="post">
username: <input type="text" name="username"><br/>
password: <input type="password" name="password"><br/>
<div th:text="${error}"></div>
<input type="submit" value="提交">
</form>
</body>
</html>
index.html
<!DOCTYPE html>
<html lang="en" xmlns:th=http://www.thymeleaf.org
xmlns:sec=http://www.thymeleaf.org/extras/spring-security
xmlns:shiro=http://www.pollix.at/thymeleaf/shiro>
<head>
<title>Title</title>
</head>
<body>
<!--<shiro:principal/>可以查看用户名-->
<h3>hello,<shiro:principal/></h3>
<h4><a th:href="@{/logout}">注销操作</a></h4>
<h4><a shiro:hasRole="admin" th:href="@{/admin}">管理员界面</a></h4>
<h4><a shiro:hasAnyRoles="admin,user" th:href="@{/user}">普通用户界面</a></h4>
</body>
</html>
五、shiro的基于数据库的认证
流程:
需要一个通过id找到密码的userService
@Service
public class UserLoginService {
@Autowired
UserLoginMapper userLoginMapper;
public UserLogin getUserLogin(String account){
return userLoginMapper.getUserLogin(account);
}
}
然后再ShiroConfig中自定义自己的Realm即可
@Configuration
public class ShiroConfig {
//注意这个地方的realm对应的包时shiro下的包
@Bean
public Realm realm(){
// TextConfigurationRealm realm = new TextConfigurationRealm();
// realm.setUserDefinitions("sang=123,user\n admin=123,admin");
// realm.setRoleDefinitions("admin=read,write\n user=read");
// return realm;
return new AuthRealm();
}
@Bean
public ShiroFilterChainDefinition ShiroFilterFactoryBean(){
DefaultShiroFilterChainDefinition chainDefinition = new DefaultShiroFilterChainDefinition();
//anon表示匿名,logout表示注销操作,authc表示需要登录操作
chainDefinition.addPathDefinition("/login","anon");
chainDefinition.addPathDefinition("/dologin","anon");
chainDefinition.addPathDefinition("/logout","logout");
chainDefinition.addPathDefinition("/**","authc");
return chainDefinition;
}
//如果在thymeleaf中不使用shiro标签的话,不需要这个bean
@Bean
public ShiroDialect shiroDialect(){
return new ShiroDialect();
}
}
class AuthRealm extends AuthorizingRealm{
@Autowired
UserLoginService userLoginService;
@Override
protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken token) throws AuthenticationException {
//认证
//从token中获取登录时的账户和密码
UsernamePasswordToken userToken = (UsernamePasswordToken)token;
String account = userToken.getUsername();
String passwordToken = new String(userToken.getPassword());
//获取数据库中的密码
String password = userLoginService.getUserLogin(account).getPassword();
//如果为空就是账号不存在,如果不相同就是密码错误,但是都抛出AuthenticationException,而不是抛出具体错误原因,免得给破解者提供帮助信息
if (passwordToken == null || !passwordToken.equals(password)){
return null;
}
//认证信息里存放账号密码, getName() 是当前Realm的继承方法,通常返回当前类名 :databaseRealm
return new SimpleAuthenticationInfo(account,password,getName());
}
@Override
protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principals) {
//授权
//这个地方也可以配置数据库,进行id和角色的匹配
return null;
}
}