| 40 | [[Image(new_login.png)]] |
| 41 | Во последната верзија, со имплементација на OAuth2 клиентот, системот овозможува најава преку користење на профил од Google, Facebook или GitHub. Конфигурацијата е додадена на веќе постоечката за Spring Security, со тоа што дополнително е имплементиран CustomOAuth2AuthenticationService сервис за справување со барањата за регистрација и најава со профил од некој од претходно споментатите провајдери. |
| 42 | {{{#!java |
| 43 | ... |
| 44 | .and() |
| 45 | .oauth2Login() |
| 46 | .loginPage("/login") |
| 47 | .permitAll() |
| 48 | .userInfoEndpoint(x -> x.userService(customOAuth2UserDetailService)) |
| 49 | .successHandler(oAuth2SuccessHandler) |
| 50 | .failureHandler(oAuth2FailureHandler) |
| 51 | ... |
| 52 | }}} |
| 53 | За правилно справување со различните формати на одговор кои секоја од страниците го враќа, преку имплементација на шаблонот за развој на софтвер Factory, имплементиравме фабрика за објекти од типот OAuth2UserDetails, при што во зависност од сценариото соодветно се преземаат потребните атрибути за да може корисникот да идентификува и/или регистрира во нашата база. |
| 54 | {{{#!java |
| 55 | public abstract class OAuth2UserDetails { |
| 56 | protected Map<String, Object> attributes; |
| 57 | |
| 58 | public OAuth2UserDetails(Map<String, Object> attributes) { |
| 59 | this.attributes = attributes; |
| 60 | } |
| 61 | |
| 62 | public abstract String getName(); |
| 63 | public abstract String getEmail(); |
| 64 | } |
| 65 | }}} |
| 66 | {{{#!java |
| 67 | public class OAuth2GoogleUser extends OAuth2UserDetails{ |
| 68 | public OAuth2GoogleUser(Map<String, Object> attributes) { |
| 69 | super(attributes); |
| 70 | } |
| 71 | |
| 72 | @Override |
| 73 | public String getName() { |
| 74 | return (String) attributes.get("name"); |
| 75 | } |
| 76 | |
| 77 | @Override |
| 78 | public String getEmail() { |
| 79 | return (String) attributes.get("email"); |
| 80 | } |
| 81 | } |
| 82 | }}} |
| 83 | {{{#!java |
| 84 | public class OAuth2GitHubUser extends OAuth2UserDetails{ |
| 85 | public OAuth2GitHubUser(Map<String, Object> attributes) { |
| 86 | super(attributes); |
| 87 | } |
| 88 | |
| 89 | @Override |
| 90 | public String getName() { |
| 91 | return (String) attributes.get("name"); |
| 92 | } |
| 93 | |
| 94 | @Override |
| 95 | public String getEmail() { |
| 96 | return (String) attributes.get("login"); |
| 97 | } |
| 98 | } |
| 99 | }}} |
| 100 | {{{#!java |
| 101 | public class OAuth2FacebookUser extends OAuth2UserDetails{ |
| 102 | public OAuth2FacebookUser(Map<String, Object> attributes) { |
| 103 | super(attributes); |
| 104 | } |
| 105 | |
| 106 | @Override |
| 107 | public String getName() { |
| 108 | return (String) attributes.get("name"); |
| 109 | } |
| 110 | |
| 111 | @Override |
| 112 | public String getEmail() { |
| 113 | return (String) attributes.get("email"); |
| 114 | } |
| 115 | } |
| 116 | }}} |
| 117 | {{{#!java |
| 118 | public class OAuth2UserDetailsFactory { |
| 119 | public static OAuth2UserDetails createOAuth2UserDetails (String registrationId, Map<String, Object> attributes) |
| 120 | { |
| 121 | if(registrationId.equals(Providers.google.name())) |
| 122 | { |
| 123 | return new OAuth2GoogleUser(attributes); |
| 124 | } |
| 125 | else if(registrationId.equals(Providers.facebook.name())) |
| 126 | { |
| 127 | return new OAuth2FacebookUser(attributes); |
| 128 | } |
| 129 | else if(registrationId.equals(Providers.github.name())) |
| 130 | { |
| 131 | return new OAuth2GitHubUser(attributes); |
| 132 | } |
| 133 | else |
| 134 | { |
| 135 | throw new RuntimeException("Login with this provider is not supported!"); |
| 136 | } |
| 137 | } |
| 138 | } |
| 139 | }}} |
| 140 | Објектот од ваков тип соодветно се креира во сервисот за автентикација преку повик на статичкиот метод. |
| 141 | {{{#!java |
| 142 | @Service |
| 143 | public class CustomOAuth2UserDetailService extends DefaultOAuth2UserService { |
| 144 | |
| 145 | private final UsersDao usersDao; |
| 146 | |
| 147 | public CustomOAuth2UserDetailService(UsersDao usersDao) { |
| 148 | this.usersDao = usersDao; |
| 149 | } |
| 150 | |
| 151 | @Override |
| 152 | public OAuth2User loadUser(OAuth2UserRequest userRequest) throws OAuth2AuthenticationException { |
| 153 | OAuth2User oAuth2User = super.loadUser(userRequest); |
| 154 | |
| 155 | try |
| 156 | { |
| 157 | return checkOAuth2User(userRequest, oAuth2User); |
| 158 | } |
| 159 | catch (AuthenticationException e) |
| 160 | { |
| 161 | throw e; |
| 162 | } |
| 163 | catch (Exception ex) |
| 164 | { |
| 165 | throw ex; |
| 166 | } |
| 167 | } |
| 168 | |
| 169 | private OAuth2User checkOAuth2User(OAuth2UserRequest oAuth2UserRequest, OAuth2User oAuth2User) |
| 170 | { |
| 171 | OAuth2UserDetails oAuth2UserDetails = OAuth2UserDetailsFactory |
| 172 | .createOAuth2UserDetails(oAuth2UserRequest.getClientRegistration().getRegistrationId(), oAuth2User.getAttributes()); |
| 173 | |
| 174 | if(ObjectUtils.isEmpty(oAuth2UserRequest)) |
| 175 | { |
| 176 | throw new RuntimeException("Cannot identifty OAuth2 user!"); |
| 177 | } |
| 178 | |
| 179 | User user = usersDao.findByUsernameAndProvider( |
| 180 | oAuth2UserDetails.getEmail(), |
| 181 | oAuth2UserRequest.getClientRegistration().getRegistrationId()); |
| 182 | User userDetails = null; |
| 183 | if(user != null) |
| 184 | { |
| 185 | userDetails = user; |
| 186 | userDetails = updateOAuth2UserDetail(userDetails, oAuth2UserDetails); |
| 187 | } |
| 188 | else |
| 189 | { |
| 190 | userDetails = registerOAuth2UserDetail(oAuth2UserRequest, oAuth2UserDetails); |
| 191 | } |
| 192 | return new OAuth2UserDetailsCustom( |
| 193 | userDetails.getUserID(), |
| 194 | userDetails.getUsername(), |
| 195 | userDetails.getPassword(), |
| 196 | Collections.singletonList(new SimpleGrantedAuthority(userDetails.getRole().getRoleName())) |
| 197 | ); |
| 198 | } |
| 199 | |
| 200 | public User registerOAuth2UserDetail(OAuth2UserRequest oAuth2UserRequest, OAuth2UserDetails oAuth2UserDetails) |
| 201 | { |
| 202 | Role r = usersDao.findById(1L); |
| 203 | User user = new User(); |
| 204 | user.setName(Objects.requireNonNullElse(oAuth2UserDetails.getName(), "")); |
| 205 | user.setEmail(oAuth2UserDetails.getEmail()); |
| 206 | user.setProvider(oAuth2UserRequest.getClientRegistration().getRegistrationId()); |
| 207 | user.setRole(r); |
| 208 | return usersDao.updateUser(user); |
| 209 | } |
| 210 | |
| 211 | public User updateOAuth2UserDetail(User user, OAuth2UserDetails oAuth2UserDetails) |
| 212 | { |
| 213 | user.setEmail(oAuth2UserDetails.getEmail()); |
| 214 | return usersDao.mergeUser(user); |
| 215 | } |
| 216 | } |
| 217 | }}} |
| 218 | По креирањето на објект од овој тип, се проверува во базата дали евентуално постои ваков корисник и доколку не, истиот се регистрира. Откако ќе се утврди неговиот статус, функцијата враќа објект од типот OAuth2UserDetailsCustom кој го имплементира UserDetails интерфејсот од Java со што се овозможува да поминува низ филтрите и да се третира исто како и регуларно најавен корисник директно на системот. Доколку најавата е успешна, како и претходно, корисникот се пренасочува кон React апликацијата. |
| 219 | {{{#!java |
| 220 | @Override |
| 221 | public void onAuthenticationSuccess(HttpServletRequest request, HttpServletResponse response, Authentication authentication) throws IOException, ServletException { |
| 222 | getRedirectStrategy().sendRedirect(request, response, "http://localhost:3000/login-callback"); |
| 223 | } |
| 224 | }}} |
| 225 | === Поврзува профил |